【问题标题】:Cannot return correct memory address from a shared lib in C无法从 C 中的共享库返回正确的内存地址
【发布时间】:2021-08-02 05:16:05
【问题描述】:

我一直在尝试实现一个小型模拟来了解 malloc() 的内存分配。我创建了一个名为 mem.c 的共享库。我将库链接到主库,但无法传递模拟“堆”的正确地址。堆是由共享库中的 malloc() 调用创建的。

共享库中的地址:0x55ddaff662a0 主地址:0xffffffffaff662a0

似乎只有最后 4 个字节是正确的。休息设置为 0xf。

但是,当我在 main 中 #include "mem.c" 时,它可以正常工作。在不包括 mem.c 的情况下如何获得相同的结果。我试图在不包括 mem.c 或 mem.h 的情况下解决这个问题。我这样创建共享库:

gcc -c -fpic mem.c
gcc -shared -o libmem.so mem.o
gcc main.c -lmem -L. -o main

【问题讨论】:

  • library called mem.c 会很不寻常。您正在尝试的一个小例子会有所帮助。此外,地址的最后 4 个字节似乎是相同的。你过得怎么样?
  • 创建一个文件mem.h,其中包含main() 中所需的任何函数的原型。在您的程序源中包含mem.h,然后链接到mem.c 的目标文件或库libmem(如果您创建了一个)。如果您只需要一个地址,您可以在程序源中使用声明为extern 的简单变量。
  • 我正在尝试不使用 #include mem.h 或 mem.c 来实现。我需要从主库和共享库中获取相同的地址值,但只能正确获取最后 4 个字节。我以这种方式创建了模拟的“堆”:char *heap = (char *)malloc(sizeof(char) * n);。我这样返回地址:return heap;。实现的 malloc 函数只返回最后 4 个字节。 @Zoso
  • 通过“共享库”,您的意思是您在运行时将您的库与 libdl 链接?
  • @humbleLearner 没有声明它认为定义是int getAddr()

标签: c linux bash pointers shared-libraries


【解决方案1】:

来自您的 cmets

我正在尝试不使用 #include mem.h 或 mem.c 来实现。

那么你必须通过其他方式为你正在调用的函数提供一个原型。在没有显式函数原型的情况下,按照 K&R 和后来的 ANSI C 的传统,未声明的函数被假定返回 int 并采用 int 类型的参数。

编辑:本质上,您需要在第一次使用该功能之前的某个地方写下您通常在标题中找到的内容。或者它是一个函数指针,你需要一个适当的变量来存储函数指针。

例如,声明一个函数,该函数返回一个无类型指针,以及你要编写的任意、未指定数量的参数

void *getAddr();

请注意,此处不需要使用 extern 关键字,因为非静态函数声明始终隐含外部链接。

如果你想在运行时动态链接(使用dlopen / LoadLibrarydlsym / GetProcAddress),你需要定义一个函数指针变量

void* (*getAddr_fptr)();

您可以使用dlsym 来设置它

*(void**)(&getAddr_fptr) = dlsym(…)

这种尴尬的写法是因为函数指针被允许具有与数据指针不同的大小和对齐方式(有关详细信息,请参见 dlsym 手册页)。

如今,在大多数平台上,int 是 4 字节类型,最常见的调用约定通过 register 传递前几个函数参数。在 x86(和 x86_64)上,寄存器是 AX、BX、CX 和 DX,可以以不同的大小访问,但可以以不同的大小读写(以允许大小转换)。这解释了为什么只传递前 4 个字节:它是通过寄存器传递的,只有对寄存器的写入是作为 4 字节宽的写入完成的。当函数然后从寄存器中读取时,它会使用更宽的类型,将较高值的位设置为全 1。

【讨论】:

  • 你可能已经成功了。我是否可以建议您在答案中添加指定原型的方式,例如 extern void *getAddr(); 以便理解 OP?
  • 这可能是确切的情况。非常感谢。您能否提供一个“通过其他方式创建函数原型”的示例? @datenwolf
【解决方案2】:

我假设您正在尝试完成这样的事情?对于mem.c

#include <stdlib.h>
#include <stdio.h>

void* getAddr() {
    char *heap = (char *)malloc(10);
    printf("%p\n", (void*)heap);
    return heap;
}

然后在不包含 mem.c 函数的任何标头的情况下,您可能会使用 mem.c 创建一个库,正如您在问题中已经提到的那样,并且在 main.c 中有如下内容

#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>

typedef void* (*getAddr)(); //prototype for getAddr() in mem.c

int main() {
    void* handle = dlopen("./libmem.so", RTLD_LAZY);
    if(handle) {
        void* fn = dlsym(handle, "getAddr");
        if(fn) {
            void* addr = ((getAddr)(fn))();
            printf("%p\n", addr);
            free(addr);
            addr = NULL;
        } else {
            printf("Failed to dlsym %s\n", dlerror());
        }
    } else {
        printf("Failed to dlopen %s\n", dlerror());
    }
}

编辑:对于@Zilog80 提到的OP 的目的,由于库与main 可执行文件链接,dlopen() 部分可以去掉,main.c 可以简化为

#include <stdio.h>
#include <stdlib.h>

extern void* getAddr(); //prototype for getAddr() in mem.c

int main() {
    void* addr = getAddr();
    printf("%p\n", addr);
    free(addr);
    addr = NULL;
}

并使用与 OP 类似的编译命令,即

gcc -shared -o libmem.so -fpic mem.c
gcc main.c -lmem -L . -o main

执行时

LD_LIBRARY_PATH=. ./main

【讨论】:

  • 出于 OP 目的,模拟 malloc,我猜 OP 不需要 dlopen 其 mem 库。使用加载器就足够了。
  • 是的,我相信不需要 dlopen 它的 mem 库。使用加载器应该足够了,但我不确定。
  • @humbleLearner 我已经编辑了我的答案。看看这是否有效。
  • @Zilog80 我已经更新了我的答案以纳入您的建议。
  • extern void* getAddr();.是的,这个实现有效,谢谢。 @Zoso
【解决方案3】:

来自cmets:

您的主代码中有getAddr 的声明吗?

不,我没有,但我试图在没有声明的情况下实施,这可能吗?

那是你的问题。如果没有声明,编译器会退回到默认声明 int getAddr()。这与返回void *的实际定义不兼容,并且通过不兼容的声明调用函数会触发undefined behavior

可能发生的事情是,当函数的返回值实际返回时,你只取回了 4 个低位字节。假设你的系统是 little-endian,int 是 4 个字节,void * 是 8 个字节,这可以解释低位是相同的。

必须在调用函数之前包含有效声明。它不一定必须驻留在头文件中,但它必须在调用发生时可见。

【讨论】:

  • 我明白了,非常感谢您的澄清。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-17
  • 2013-10-01
  • 1970-01-01
  • 2021-02-11
相关资源
最近更新 更多