странное поведение при попытке скомпилировать исходный код с помощью tcc против сгенерированного gcc файла .o

Я пытаюсь скомпилировать исходный код с помощью tcc (версия 0.9.26) для файла .o, сгенерированного gcc, но он ведет себя странно. GCC (версия 5.3.0) взят из 64-разрядной версии MinGW.

В частности, у меня есть следующие два файла (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!!!

Обратите внимание, что он отрезал 4 байта от строки 1234567in dummy!!!\n. Интересно, что могло пойти не так?

Спасибо Джин

========файл 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 (te1.c, скомпилированный tcc) и te1_gcc.o (te1.c, скомпилированный gcc). В скомпилированном 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

person packetie    schedule 12.07.2016    source источник
comment
Скорее всего, tcc и gcc имеют разные соглашения о вызовах по умолчанию. Может захочется это проверить.   -  person Captain Obvlious    schedule 12.07.2016
comment
Должен ли te1.c иметь extern void printf1(char *p); или включать заголовок, объявляющий printf()?   -  person RastaJedi    schedule 12.07.2016
comment
Побочная проблема: extern void dummy(); должно быть extern void dummy(void);, иначе main() может вызвать dummy(1,2,3) без предупреждения/ошибки.   -  person chux - Reinstate Monica    schedule 12.07.2016
comment
Соглашение о вызовах - это то, о чем я беспокоюсь, хотелось бы, чтобы был окончательный ответ.   -  person packetie    schedule 12.07.2016
comment
Добавил extern void printf1(char *p);, но это ничего не изменило. Спасибо, что указали extern void dummy(void); Попробовал, но безрезультатно.   -  person packetie    schedule 12.07.2016
comment
Какие релокации для вашего te1.o (readelf -r te1.o)? Вы пытались скомпилировать te2.c с помощью tcc, но связать с gcc? Возможны три случая: 1) te1.o неверен 2) tcc-linker делает что-то не так, 3) что-то еще. Было бы неплохо исключить 1+2..   -  person ead    schedule 16.07.2016
comment
Я не могу воспроизвести вашу ошибку в Linux. Разница может быть в том, что мне не нужен objcopy.   -  person ead    schedule 16.07.2016
comment
Я думаю, что компилятор tcc и компоновщик что-то другое, для вызова функции printf1(..) он перевел его в lea -0x4(%rip),%rcx. Обратите внимание на -4? Это объясняет, если tcc связывает te1.o, скомпилированный с помощью gcc, то в указателе на константную строку отстает 4 байта. Да, я тестировал его на Linux, и tcc и gcc тоже хорошо работали вместе. Мой linux gcc версии (4.8.4) отличается от версии для Windows (5.3.0). objdump -d показано, что эти две версии gcc генерируют разный код.   -  person packetie    schedule 16.07.2016
comment
Не могли бы вы показать нам переезды (readelf -r te1.o)? -4 может иметь значение только тогда, когда мы знаем перемещение. В linux gcc использует релокацию R_X86_64_32, а tcc R_X86_64_PC32, может тут что-то перепутал. Я также попытался бы связать с другим компоновщиком (не tcc).   -  person ead    schedule 16.07.2016
comment
ваш эксперимент исключает возможность 3) что-то еще почти исключено. Теперь нам нужно выяснить, поврежден ли объектный файл или компоновщик работает неправильно...   -  person ead    schedule 16.07.2016
comment
@ead опубликовал вывод objdump -r te1.o. Не знаю, ошибся ли tcc, но он определенно делает что-то другое, как видно из кода сборки lea -4(%rip), %rcx, который он генерирует.   -  person packetie    schedule 17.07.2016
comment
Спасибо, я вообще не знаю pe-формата. Но для эльфийского формата релокация должна быть 000000000000000b R_X86_64_PC32 .rdata-4   -  person ead    schedule 17.07.2016


Ответы (2)


Не имеет ничего общего с tcc или соглашениями о вызовах. Это связано с различными соглашениями компоновщика для форматов elf64-x86-64 and pe-x86-64.

С PE компоновщик будет неявно вычитать 4, чтобы вычислить окончательное смещение.

С ELF этого не происходит. Из-за этого 0 является правильным начальным значением для PE, а -4 — для ELF.

К сожалению, objcopy не конвертирует это -> ошибка в objcopy.

person h1n1    schedule 17.07.2016
comment
Это имело бы большой смысл! Не могли бы вы предоставить несколько ссылок? Меня больше всего интересует, что это связано с различными соглашениями компоновщика для форматов elf64-x86-64 и pe-x86-64. - person ead; 17.07.2016
comment
См. также: sourceware.org/bugzilla/show_bug.cgi?id=970. - не будет исправлено, см.: sourceware.org/ml/binutils/1999-q3/msg00611 .html - person h1n1; 17.07.2016
comment
Я заметил, что при использовании 32-битного компилятора + objcopy ошибки нет. Возможно, это обходной путь, пока они, надеюсь, исправляют objcopy. - person Jean-François Fabre; 17.07.2016
comment
Создайте DLL с помощью GCC (или другого компилятора) и свяжите ее с помощью TCC. - person h1n1; 17.07.2016
comment
Похоже, что использовать objcopy для преобразования 64-битных объектных файлов — плохая идея. Я попробовал несколько преобразований на своей Linux-машине, и многие из них были мусором. 32-битная версия может быть другой историей, потому что это другая модель кода. - person ead; 17.07.2016
comment
Этот проект должен будет обрабатывать файл размером в несколько ГБ, поэтому лучше использовать 64-битное приложение. Я прочитал это https://sourceware.org/ml/binutils/1999-q3/msg00611.html и оказалось, что решить проблему с помощью objcopy непросто. Я не могу построить dll, так как источники будут вызывать функции друг из друга (для выполнения требуется окончательная компоновка). Причина, по которой мне пришлось использовать objcopy (отсюда и проблемы), заключается в том, что я не могу заставить MinGW gcc выводить объектный файл в формате elf. Интересно, знаете ли вы, ребята, обходной путь, чтобы сделать это? Спасибо. - person packetie; 17.07.2016
comment
@codingFun gcc может форматировать в формате elf (в конце концов, это то, что он делает в Linux). Вы можете собрать gcc из исходников с флагом кросс-компилятора --target=elf-something (точно не помню), тогда он будет создавать файлы elf-object - person ead; 18.07.2016
comment
Спасибо @ead. Это хорошо знать. Жаль, что есть двоичный файл, который я могу просто скачать. Компиляция в Windows всегда доставляет много хлопот. - person packetie; 20.07.2016

добавлять

extern void printf1(char *p);

в ваш файл te1.c

Или: компилятор будет считать аргумент 32-битным целым числом, поскольку прототипа нет, а указатели имеют длину 64 бита.

Изменить: это все еще не работает. Я обнаружил, что функция никогда не возвращается (поскольку вызов printf1 во второй раз ничего не делает!). Кажется, что первые 4 байта используются как обратный адрес или что-то в этом роде. В 32-битном режиме gcc работает нормально. Звучит как проблема соглашения о вызовах для меня, но до сих пор не могу понять. Еще одна подсказка: вызов printf со стороны te1.c (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
person Jean-François Fabre    schedule 12.07.2016
comment
%c3%a7ois-fabre Спасибо за предложение, я попробовал, но получил тот же результат, что и раньше. - person packetie; 12.07.2016
comment
По крайней мере, я могу воспроизвести ошибку! - person Jean-François Fabre; 13.07.2016
comment
Это здорово знать! Пожалуйста, дайте мне знать, что вы узнали. Я собираюсь добавить награду за это :-) - person packetie; 13.07.2016
comment
В моем случае от te2.exe я увидел следующее lea 0x2fa5(%rip),%rcx # 0x404004 Смещение должно было быть 0x404000, где начинается строка. дополнительные 4 могут быть связаны с lea -0x4(%rip),%rcx (обновление 1 из ОП). Кажется, это ошибка на tcc, но я не уверен. - person packetie; 15.07.2016