尝试使用 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

【问题讨论】:

tccgcc 可能有不同的默认调用约定。可能想检查一下。 应该te1.cextern 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.exe lea 0x2fa5(%rip),%rcx # 0x404004 看到以下内容,偏移量应该是 0x404000,字符串开始的位置。额外的 4 可能与 lea -0x4(%rip),%rcx 相关(来自 OP 的更新 1)。似乎是 tcc 上的一个 bug,但我不确定。

以上是关于尝试使用 tcc 针对 gcc 生成的 .o 文件编译源时的奇怪行为的主要内容,如果未能解决你的问题,请参考以下文章

linux下的gcc编译器

GCC编译流程

gcc 简单使用笔记

a的用gcc编译

GCC编译流程及常用编辑命令

Linux(程序设计):05---gcc的基本用法