8

假设我有一个没有外部依赖的 C 文件,只有 const 数据部分。我想编译这个文件,然后得到一个可以加载到另一个程序中的二进制 blob,该函数将通过函数指针使用。

举个例子,这是一个虚构的二进制模块,f1.c

static const unsigned char mylut[256] = {
    [0 ... 127] = 0,
    [128 ... 255] = 1,
};

void f1(unsigned char * src, unsigned char * dst, int len)
{
    while(len) {
        *dst++ = mylut[*src++];
        len--;
    }
}

我想把它编译成 f1.o,然后是 f1.bin,然后在 prog.c 中像这样使用它

int somefunc() {
    unsigned char  * codedata;
    f1_type_ptr  f1_ptr;
    /* open f1.bin, and read it into codedata */

    /* set function pointer to beginning of loaded data */
    f1_ptr =(f1_type_ptr)codedata;

    /* call !*/
    f1_ptr(src, dst, len);
}

我想从 f1.c 到 f1.o 涉及 -fPIC 以获得位置独立性。我可以使用哪些标志或链接描述文件从 f1.o 转到 f1.bin ?

澄清:

我知道动态链接。在这种情况下,动态链接是不可能的。如果可能的话,链接步骤必须将 func 指针转换为加载的数据

请假设没有操作系统支持。如果可以的话,我会在汇编中写 f1 与 PC 相关的地址。

4

3 回答 3

14

首先,正如其他人所说,您应该考虑使用 DLL 或 SO。

也就是说,如果你真的想这样做,你需要替换链接描述文件。像这样的东西(没有很好的测试,但我认为它有效):

ENTRY(_dummy_start)
SECTIONS
{
    _dummy_start = 0;
    _GLOBAL_OFFSET_TABLE_ = 0;
    .all : { 
        _all = .;
        LONG(f1 - _all);
        *( .text .text.* .data .data.* .rodata .rodata.* ) 
    }
}

然后编译:

$ gcc -c -fPIC test.c

链接:

$ ld -T script.ld test.o -o test.elf

并使用以下命令提取二进制 blob:

$ objcopy -j .all -O binary test.elf test.bin

可能欢迎对脚本进行一些解释:

  • ENTRY(_dummy_start)这只是避免了关于程序没有入口点的警告。
  • _dummy_start = 0;这定义了上一行中使用的符号。该值未使用。
  • _GLOBAL_OFFSET_TABLE_ = 0;这可以防止另一个链接器错误。我不认为你真的需要这个符号,所以它可以定义为 0。
  • .all这是将收集 blob 的所有字节的部分的名称。在此示例中,所有.text,.data.rodata部分将放在一起。如果您有复杂的功能,您可能需要更多,在这种情况下objdump -x test.o是您的朋友。
  • LONG(f1 - _all)不是真的需要,但是您想知道函数在 blob 中的偏移量,不是吗?您不能假设它将位于偏移量 0。使用这一行,blob 中的前 4 个字节将是符号f1(您的函数)的偏移量。如果使用 64 位指针,请更改LONG为。QUAD

更新:现在是一个快速的'n'dirty测试(它有效!):

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>

typedef void (*f1_t)(char *a, char *b, int len);
f1_t f1;

int main()
{
    char *blob = (char*)valloc(4096);
    FILE *f = fopen("test.bin", "rb");
    fread(blob, 1, 4096, f);
    fclose(f);

    unsigned offs = *(unsigned*)blob;
    f1 = (f1_t)(blob + offs);
    mprotect(blob, 4096, PROT_READ | PROT_WRITE | PROT_EXEC);
    char txt[] = "¡hello world!";
    char txt2[sizeof(txt)] = "";
    f1(txt, txt2, sizeof(txt) - 1);
    printf("%s\n%s\n", txt, txt2);
    return 0;

}
于 2012-08-27T09:13:11.797 回答
2

您应该考虑构建一个共享库(Windows为.dll ,Linux 为.so)。

像这样构建库:

gcc -c -fPIC test.c
gcc -shared test.o -o libtest.so

如果您想从代码中动态加载库,请查看函数dlopen(3)dlsym(3)

或者,如果您想在编译时链接库,请使用

gcc -c main.c
gcc main.o -o <binary name> -ltest

编辑:

我真的不确定我会在这里说什么,但这可以为您提供研究进展的线索...

如果您不想使用dlopendlsym,可以尝试从.o文件中读取符号表以找到函数地址,然后将具有读取和执行权限的目标文件映射到内存中然后你应该能够在你找到的地址执行加载的代码。但是要小心你可能在这段代码中遇到的其他依赖项。

您可以查看手册页elf(5)

于 2012-08-27T08:22:55.427 回答
0

使用强制转换函数指针。

这是一个例子:

#include <stdio.h>

int main()
{
    unsigned char *dst, *src;
    int len;
    void (*f1)(unsigned char *, unsigned char *, int);
    *(void **)(&f1) = 0x..........;
    f1(src,dst,len);
    return 0;
}

要做更多,你真的需要一个链接器和一个动态加载器。

于 2019-03-03T14:58:46.600 回答