2012-01-15 73 views
4

我只是有一个有趣的想法。我使用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; 
} 

文件2: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 

回答

1

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

所以是的,理论上你可以创建一个可执行文件,它调用另一个可执行文件的函数,但不仅仅是一个普通的连接线。你的主要问题是第二个可执行文件不能有一个入口点 - 一个main函数 - 并且仍然与原文链接(因为名字会碰撞)。

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

但是,从二进制文件调用函数的最简单方法是只与目标文件链接而不是可执行文件。

+0

我对共享库和LD_PRELOAD方法感兴趣。有什么更详细的地方可以阅读吗? – Patrick 2012-01-15 07:12:39

+1

http://stackoverflow.com/questions/426230/what-is-the-ld-preload-trick可能会帮助你... – Jason 2012-01-15 07:18:06

+1

以下是一些更多信息:http://lca2009.linux.org.au/slides /172.pdf – Jason 2012-01-15 07:20:47

1

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

虽然假设您已经转储的对象文件中的函数遵循ABI和您正在使用的平台/编译器的调用约定(我知道一个看起来很明显),但它不能包括它自己的入口点(即,一个main()函数)。关于第二点,目标文件必须基本上是独立函数的“库”。这意味着你不能链接到一个可执行文件。

+0

我知道我可以很容易地链接到.o文件。虽然,我想知道我是否可以用可执行文件来做到这一点。 – Patrick 2012-01-15 06:52:36

+1

啊,你没有提到你的目标文件包含'main()'...这是行不通的,它必须是一个函数库的目标文件 – Jason 2012-01-15 06:53:56

+0

有人应该提到编译器之间的区别。 – Mikhail 2012-01-15 06:55:16

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 [email protected]_2.0 (2) 
    39: 00000000  0 FILE LOCAL DEFAULT ABS main.c 
    51: 00000000  0 FUNC GLOBAL DEFAULT UND [email protected]@GLIBC_ 
    62: 080483c4 29 FUNC GLOBAL DEFAULT 13 main 

现在你可以看到主函数的地址已经搬迁到0x80483c4,这是函数的运行时地址码。生成a.out不能再与其他人链接,因为可能存在运行时地址冲突。

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

欲了解更多详情,我建议您阅读Computer System: A Programmer's Prospective书中的链接章节,其中涵盖了很多链接和重新定位。

+0

感谢您推荐这本书!我会研究它。 – Patrick 2012-01-15 07:13:11

+0

+1的详细解释。 – 2012-01-15 07:18:23

相关问题