是否可以链接普通二进制文件中的某些函数?

时间:2012-01-15 06:42:45

标签: c gcc

我只是有一个有趣的想法。我使用objdump转储一个简单的二进制文件,我在二进制文件中看到很多函数。是否可以创建另一个与这些功能链接的C程序?假设我知道输入和输出的参数。

更多信息: 文件1:test.c的

#include <stdio.h>

int add(int x,int y)
{
    return x+y;
}

int main(int argc, const char *argv[])
{
    printf("%d\n",add(3,4));
    return 0;
}

file2:test1.c

#include <stdio.h>

int main(int argc, const char *argv[]) 
{
    printf("%d\n",add(8,8));
    return 0; 
}

gcc test.c -o test.exe
gcc test1.c test.exe -o test1.exe

输出:

ld: in test.exe, can't link with a main executable
collect2: ld returned 1 exit status

3 个答案:

答案 0 :(得分:2)

我不敢。

编译后的二进制文件已由链接器通过重定位阶段处理,链接器将代码中的每个符号引用与运行时地址相关联。

你可以做一个简单的实验来找出差异,这是一个输出'Hello World'的程序:

// main.c
#include <stdio.h>

int main()
{
    printf("Hello World!");
    return 0;
}

使用gcc -c,您可以将源代码编译为可重定位目标文件:

$ gcc -c main.o

$ readelf -s main.o

Symbol table '.symtab' contains 10 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000     0 FILE    LOCAL  DEFAULT  ABS main.c
     2: 00000000     0 SECTION LOCAL  DEFAULT    1 
     3: 00000000     0 SECTION LOCAL  DEFAULT    3 
     4: 00000000     0 SECTION LOCAL  DEFAULT    4 
     5: 00000000     0 SECTION LOCAL  DEFAULT    5 
     6: 00000000     0 SECTION LOCAL  DEFAULT    7 
     7: 00000000     0 SECTION LOCAL  DEFAULT    6 
     8: 00000000    29 FUNC    GLOBAL DEFAULT    1 main
     9: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND printf

你可以从这里看到函数main的值是0x0,这意味着它还没有重新定位,可以与其他人联系。

但是当您使用gcc命令编译文件时,要生成可执行文件:

$ gcc main.c
$ readelf -s a.out | grep main
     2: 00000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.0 (2)
    39: 00000000     0 FILE    LOCAL  DEFAULT  ABS main.c
    51: 00000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@@GLIBC_
    62: 080483c4    29 FUNC    GLOBAL DEFAULT   13 main

现在您可以看到函数main的地址已重新定位到0x80483c4,这是功能代码的运行时地址。生成a.out无法再与其他人链接,因为可能存在运行时地址违规。

一般来说,重定位阶段无法恢复,因为某些符号信息会在阶段后丢失。

有关详细信息,我建议您阅读书籍Computer System: A Programmer's Prospective中的链接章节,其中涵盖了链接和重新定位的很多内容。

答案 1 :(得分:1)

当然,只需编写一个头文件,为您要使用的函数提供正确的函数签名声明,然后在您调用函数的C代码模块中包含该头文件。然后编译并链接其他目标文件以创建最终的可执行文件。

但是假设您转储的目标文件中的函数遵循您正在使用的平台/编译器的ABI和调用约定(我知道这似乎很明显),并且它不能包含它自己的入口点(即main()函数)。关于第二点,目标文件必须基本上是独立函数的“库”。这意味着您无法链接可执行文件。

答案 2 :(得分:1)

从实际角度来看,对象(.o)文件和可执行文件之间几乎没有区别。目标文件可以包含未绑定的符号,可执行文件不能。可执行文件必须包含一个入口点,其中目标文件没有这样的限制。可执行文件具有更完整的标头。可执行文件还解决了所有的跳转偏移,因为它已经通过链接解析阶段。某些功能可能已被永久内联。

所以是的,从理论上讲,你可以创建一个可执行文件,从另一个可执行文件调用函数,但不只是使用普通的链接行。您的主要问题是第二个可执行文件不能有一个入口点 - main函数 - 并且仍然与原始函数链接(因为名称会发生​​冲突)。

如果您的目标只是调用原始函数,我建议使用与您似乎建议的直接链接不同的方法。如果您创建一个共享库并将其放入LD_PRELOAD环境变量中,然后调用原始可执行文件,您可以使用您的库有效地挂钩程序条目(可能通过_main符号),然后调用备用程序常规。因为这个库与原始二进制文件一起加载,所以你可以调用所有原始函数......

但到目前为止,从二进制文件中调用函数的最简单方法就是链接目标文件而不是可执行文件。