尝试使用 tcc 针对 gcc 生成的 .o 文件编译源时的奇怪行为
Posted
技术标签:
【中文标题】尝试使用 tcc 针对 gcc 生成的 .o 文件编译源时的奇怪行为【英文标题】:strange behavior when trying to compile a source with tcc against gcc generated .o file 【发布时间】:2016-07-12 19:12:41 【问题描述】:我正在尝试使用 tcc(版本 0.9.26)针对 gcc 生成的 .o 文件编译源代码,但它的行为很奇怪。 gcc (ver 5.3.0) 来自 MinGW 64 位。
更具体地说,我有以下两个文件(te1.c te2.c)。我在 windows7 盒子上做了以下命令
c:\tcc> gcc -c te1.c
c:\tcc> objcopy -O elf64-x86-64 te1.o #this is needed because te1.o from previous step is in COFF format, tcc only understand ELF format
c:\tcc> tcc te2.c te1.o
c:\tcc> te2.exe
567in dummy!!!
请注意,它从字符串 1234567in dummy!!!\n
中截断了 4 个字节。不知道是不是出了什么问题。
谢谢 金
========文件te1.c============
#include <stdio.h>
void dummy ()
printf1("1234567in dummy!!!\n");
========文件te2.c============
#include <stdio.h>
void printf1(char *p)
printf("%s\n",p);
extern void dummy();
int main(int argc, char *argv[])
dummy();
return 0;
更新 1
看到 te1.o(由 tcc 编译的 te1.c)和 te1_gcc.o(由 gcc 编译的 te1.c)之间的汇编差异。在tcc编译的时候看到lea -0x4(%rip),%rcx
,在gcc编译的时候看到lea 0x0(%rip),%rcx
。
不知道为什么。
C:\temp>objdump -d te1.o
te1.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <dummy>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 81 ec 20 00 00 00 sub $0x20,%rsp
b: 48 8d 0d fc ff ff ff lea -0x4(%rip),%rcx # e <dummy+0xe>
12: e8 fc ff ff ff callq 13 <dummy+0x13>
17: c9 leaveq
18: c3 retq
19: 00 00 add %al,(%rax)
1b: 00 01 add %al,(%rcx)
1d: 04 02 add $0x2,%al
1f: 05 04 03 01 50 add $0x50010304,%eax
C:\temp>objdump -d te1_gcc.o
te1_gcc.o: file format pe-x86-64
Disassembly of section .text:
0000000000000000 <dummy>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 83 ec 20 sub $0x20,%rsp
8: 48 8d 0d 00 00 00 00 lea 0x0(%rip),%rcx # f <dummy+0xf>
f: e8 00 00 00 00 callq 14 <dummy+0x14>
14: 90 nop
15: 48 83 c4 20 add $0x20,%rsp
19: 5d pop %rbp
1a: c3 retq
1b: 90 nop
1c: 90 nop
1d: 90 nop
1e: 90 nop
1f: 90 nop
更新2
使用二进制编辑器,我更改了 te1.o(由 gcc 生成)中的机器代码并将 lea 0(%rip),%rcx
更改为 lea -0x4(%rip),%rcx
并使用 tcc 链接它,生成的 exe 工作正常。
更准确地说,我做到了
c:\tcc> gcc -c te1.c
c:\tcc> objcopy -O elf64-x86-64 te1.o
c:\tcc> use a binary editor to the change the bytes from (48 8d 0d 00 00 00 00) to (48 8d 0d fc ff ff ff)
c:\tcc> tcc te2.c te1.o
c:\tcc> te2
1234567in dummy!!!
更新 3
根据要求,这是objdump -r te1.o
的输出
C:\temp>gcc -c te1.c
C:\temp>objdump -r te1.o
te1.o: file format pe-x86-64
RELOCATION RECORDS FOR [.text]:
OFFSET TYPE VALUE
000000000000000b R_X86_64_PC32 .rdata
0000000000000010 R_X86_64_PC32 printf1
RELOCATION RECORDS FOR [.pdata]:
OFFSET TYPE VALUE
0000000000000000 rva32 .text
0000000000000004 rva32 .text
0000000000000008 rva32 .xdata
C:\temp>objdump -d te1.o
te1.o: file format pe-x86-64
Disassembly of section .text:
0000000000000000 <dummy>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 83 ec 20 sub $0x20,%rsp
8: 48 8d 0d 00 00 00 00 lea 0x0(%rip),%rcx # f <dummy+0xf>
f: e8 00 00 00 00 callq 14 <dummy+0x14>
14: 90 nop
15: 48 83 c4 20 add $0x20,%rsp
19: 5d pop %rbp
1a: c3 retq
1b: 90 nop
1c: 90 nop
1d: 90 nop
1e: 90 nop
1f: 90 nop
【问题讨论】:
tcc
和 gcc
可能有不同的默认调用约定。可能想检查一下。
应该te1.c
有extern void printf1(char *p);
还是包含一个声明printf()
的标头?
附带问题:extern void dummy();
应该是 extern void dummy(void);
,否则 main()
可以调用 dummy(1,2,3)
而没有警告/错误。
调用约定是我担心的事情,希望有一个明确的答案。
添加了extern void printf1(char *p);
,但没有任何区别。感谢您指出extern void dummy(void);
试过了,但是没有任何区别。
【参考方案1】:
与tcc
或调用约定无关。它与elf64-x86-64 and pe-x86-64
格式的不同链接器约定有关。
使用 PE,链接器将隐式减去 4 以计算最终偏移量。
使用 ELF,它不会这样做。因此,0 是 PE 的正确初始值,-4 是 ELF 正确的初始值。
不幸的是,objcopy
没有转换这个 -> objcopy
中的错误。
【讨论】:
这很有意义!你能提供一些链接吗?我最感兴趣的是“它与 elf64-x86-64 和 pe-x86-64 格式的不同链接器约定有关。” 参见:sourceware.org/bugzilla/show_bug.cgi?id=970 - wontfix,参见:sourceware.org/ml/binutils/1999-q3/msg00611.html 我注意到使用 32 位编译器 + objcopy 不存在该错误。也许解决方法是这样,虽然他们希望修复 objcopy。 用 GCC(或其他编译器)构建一个 DLL 并用 TCC 链接到它。 使用 objcopy 转换 64 位对象文件似乎是个坏主意。我在我的 linux 机器上尝试了一些转换,其中很多都是垃圾。 32 位可能是另一回事,因为这是另一种代码模型。【参考方案2】:添加
extern void printf1(char *p);
到您的 te1.c 文件
或者:编译器将假定参数为 32 位整数,因为没有原型,并且指针是 64 位长。
编辑:这仍然不起作用。我发现该函数永远不会返回(因为第二次调用 printf1 什么也没做!)。似乎前 4 个字节被用作返回地址或类似的东西。在 gcc 32 位模式下它工作正常。
对我来说听起来像是一个调用约定问题,但仍然无法弄清楚。
另一个线索:从te1.c
端调用printf
(gcc,使用 tcc stdlib 绑定)与 segv 崩溃。
我反汇编了可执行文件。第一部分是从 tcc 端重复调用
40104f: 48 8d 05 b3 0f 00 00 lea 0xfb3(%rip),%rax # 0x402009
401056: 48 89 45 f8 mov %rax,-0x8(%rbp)
40105a: 48 8b 4d f8 mov -0x8(%rbp),%rcx
40105e: e8 9d ff ff ff callq 0x401000
401063: 48 8b 4d f8 mov -0x8(%rbp),%rcx
401067: e8 94 ff ff ff callq 0x401000
40106c: 48 8b 4d f8 mov -0x8(%rbp),%rcx
401070: e8 8b ff ff ff callq 0x401000
401075: 48 8b 4d f8 mov -0x8(%rbp),%rcx
401079: e8 82 ff ff ff callq 0x401000
40107e: e8 0d 00 00 00 callq 0x401090
401083: b8 00 00 00 00 mov $0x0,%eax
401088: e9 00 00 00 00 jmpq 0x40108d
40108d: c9 leaveq
40108e: c3 retq
第二部分重复(6 次)调用相同的函数。如您所见,地址不同(移动了 4 个字节,就像您的数据一样)!!!它只工作一次,因为前 4 条指令如下:
401000: 55 push %rbp
401001: 48 89 e5 mov %rsp,%rbp
如果这些被跳过,那么堆栈将被销毁!
40109f: 48 89 45 f8 mov %rax,-0x8(%rbp)
4010a3: 48 8b 45 f8 mov -0x8(%rbp),%rax
4010a7: 48 89 c1 mov %rax,%rcx
4010aa: e8 55 ff ff ff callq 0x401004
4010af: 48 8b 45 f8 mov -0x8(%rbp),%rax
4010b3: 48 89 c1 mov %rax,%rcx
4010b6: e8 49 ff ff ff callq 0x401004
4010bb: 48 8b 45 f8 mov -0x8(%rbp),%rax
4010bf: 48 89 c1 mov %rax,%rcx
4010c2: e8 3d ff ff ff callq 0x401004
4010c7: 48 8b 45 f8 mov -0x8(%rbp),%rax
4010cb: 48 89 c1 mov %rax,%rcx
4010ce: e8 31 ff ff ff callq 0x401004
4010d3: 48 8b 45 f8 mov -0x8(%rbp),%rax
4010d7: 48 89 c1 mov %rax,%rcx
4010da: e8 25 ff ff ff callq 0x401004
4010df: 48 8b 45 f8 mov -0x8(%rbp),%rax
4010e3: 48 89 c1 mov %rax,%rcx
4010e6: e8 19 ff ff ff callq 0x401004
4010eb: 90 nop
【讨论】:
%c3%a7ois-fabre 感谢您的建议,我试过了,但得到的结果和以前完全一样。 我至少可以重现这个错误! 很高兴知道!请让我知道你发现了什么。我将为此添加赏金:-) 就我而言,我从 te2.exelea 0x2fa5(%rip),%rcx # 0x404004
看到以下内容,偏移量应该是 0x404000,字符串开始的位置。额外的 4 可能与 lea -0x4(%rip),%rcx
相关(来自 OP 的更新 1)。似乎是 tcc 上的一个 bug,但我不确定。以上是关于尝试使用 tcc 针对 gcc 生成的 .o 文件编译源时的奇怪行为的主要内容,如果未能解决你的问题,请参考以下文章