Как скомпилировать и выгрузить сборку для библиотеки c (string.h)? - PullRequest
1 голос
/ 06 февраля 2020

Для школьного проекта я должен выполнить большое количество манипуляций со строками в сборке. Поскольку это трудная задача, я пытался найти инновационные способы использования уже запрограммированных строковых операций. Моя идея состоит в том, чтобы скомпилировать и выгрузить сборку из библиотеки string.h в c. Затем я скопировал бы вставленную копию в мою программу. После определения местоположения памяти каждой функции и ее параметров, я полагаю, я по сути смогу вызвать функцию.

Чтобы вывести сборку, я сначала написал программу, которая включала нужные мне библиотеки:

#include <stdio.h>
#include <string.h>

int main() {

  return 0;
}

Затем я скомпилировал и выгрузил сборку, используя

gcc -o lib lib.c
objdump -d  *o

Когда я посмотрел на вывод, я заметил, что он не включает сборку для библиотек. Я предполагаю, что есть либо оптимизация компилятора, которая не включает неиспользуемые функции, либо вывод библиотеки скрыт, когда я использую objdump:

lib:    file format Mach-O 64-bit x86-64

Disassembly of section __TEXT,__text:
__text:
100000fa0:  55  pushq   %rbp
100000fa1:  48 89 e5    movq    %rsp, %rbp
100000fa4:  31 c0   xorl    %eax, %eax
100000fa6:  c7 45 fc 00 00 00 00    movl    $0, -4(%rbp)
100000fad:  5d  popq    %rbp
100000fae:  c3  retq

_main:
100000fa0:  55  pushq   %rbp
100000fa1:  48 89 e5    movq    %rsp, %rbp
100000fa4:  31 c0   xorl    %eax, %eax
100000fa6:  c7 45 fc 00 00 00 00    movl    $0, -4(%rbp)
100000fad:  5d  popq    %rbp
100000fae:  c3  retq

В качестве примечания я использую OSX Catalina, но Я могу переключиться на Ubuntu или другую ОС, если это будет проще.

Как я могу go о выводе asm для библиотеки string.h?

Ответы [ 3 ]

4 голосов
/ 06 февраля 2020

Во-первых, позвольте мне начать с того, что это действительно XY проблема .

Моя идея состоит в том, чтобы скомпилировать и выгрузить сборку из библиотеки string.h в c. Затем я скопировал и вставил выгруженную сборку в мою программу.

Вы должны , а не сделать это. Стандартная библиотека имеет очень тщательно оптимизированные функции, которые необходимо обрабатывать с осторожностью и которые очень , очень сложны. Другими словами, они в основном бесполезны в образовательных целях, если вы изучаете ассемблер.

Вам просто нужно написать свою любимую реализацию в C и затем скомпилировать ее.


* 1021 Файл заголовка (такой как string.h) обычно не содержит определения функций. Он содержит только их декларацию. Реальные функции на самом деле уже скомпилированы в динамический c объект библиотеки, который установлен в вашей системе (то есть в самой библиотеке).

Когда вы компилируете программу, компилятор автоматически связывает его со стандартной библиотекой C. Согласно этого ответа , в OS X стандартная библиотека должна находиться в /usr/lib/libSystem.B.dylib. В Ubuntu обычно это /lib/x86_64-linux-gnu/libc.so.6. Следующее относится к обеим платформам без проблем.

Если вы хотите взглянуть на разборку конкретной функции библиотеки, вы можете запустить objdump в библиотеке, передав ее в less, а затем выполнить поиск для имени функции:

$ objdump -d /usr/lib/libSystem.B.dylib | less

Находясь внутри less, вы можете выполнить поиск, набрав /, а затем имя функции, а затем нажмите Введите и используйте n или N для навигации по совпадениям.

Кроме того, вы можете вывести вывод objdump в файл и проверить его с помощью текстового редактора:

 $ objdump -d /usr/lib/libSystem.B.dylib > libSystem.disasm

Проблема, связанная с такими вещами, заключается в том, что в стандартной библиотеке есть много разных и более сложных имен для стандартных функций, чем те, которые вы видите в string.h. Внутри используются разные символы. Например, в Linux при использовании printf соответствующий символ в lib c фактически равен __printf. См. здесь , например.

Вы можете найти реальное имя символа стандартной библиотечной функции, скомпилировав программу, которая ее использует, и просмотрев разобранный код, например:

#include <string.h>
#include <stdio.h>

int main(void) {
    char s[100];

    scanf("%99s", s);
    size_t len = strlen(s);

    return 0;
}

Затем выполните:

$ gcc prog.c
$ objdump -d a.out
...
0000000000000720 <main>:
 720:   55                      push   %rbp
 721:   48 89 e5                mov    %rsp,%rbp
 724:   48 83 ec 70             sub    $0x70,%rsp
 728:   48 8d 45 90             lea    -0x70(%rbp),%rax
 72c:   48 89 c6                mov    %rax,%rsi
 72f:   48 8d 3d ae 00 00 00    lea    0xae(%rip),%rdi        # 7e4 <_IO_stdin_used+0x4>
 736:   b8 00 00 00 00          mov    $0x0,%eax
 73b:   e8 90 fe ff ff          callq  5d0 <__isoc99_scanf@plt>
 740:   48 8d 45 90             lea    -0x70(%rbp),%rax
 744:   48 89 c7                mov    %rax,%rdi
 747:   e8 74 fe ff ff          callq  5c0 <strlen@plt>
 74c:   48 89 45 f8             mov    %rax,-0x8(%rbp)
 750:   b8 00 00 00 00          mov    $0x0,%eax
 755:   c9                      leaveq
 756:   c3                      retq
 757:   66 0f 1f 84 00 00 00    nopw   0x0(%rax,%rax,1)
 75e:   00 00

И вы можете видеть, что в моем случае scanf на самом деле __isoc99_scanf, в то время как strlen не изменяется.

Затем я могу посмотреть разборку strlen, которая на Моя система (Ubuntu) выглядит следующим образом:

$ objdump -d /lib/x86_64-linux-gnu/libc.so.6 | less
...
0000000000080650 <strlen@@GLIBC_2.2.5>:
   80650:       66 0f ef c0             pxor   %xmm0,%xmm0
   80654:       66 0f ef c9             pxor   %xmm1,%xmm1
   80658:       66 0f ef d2             pxor   %xmm2,%xmm2
   8065c:       66 0f ef db             pxor   %xmm3,%xmm3
   80660:       48 89 f8                mov    %rdi,%rax
   80663:       48 89 f9                mov    %rdi,%rcx
   80666:       48 81 e1 ff 0f 00 00    and    $0xfff,%rcx
   8066d:       48 81 f9 cf 0f 00 00    cmp    $0xfcf,%rcx
   80674:       77 6a                   ja     806e0 <strlen@@GLIBC_2.2.5+0x90>
   80676:       f3 0f 6f 20             movdqu (%rax),%xmm4
   8067a:       66 0f 74 e0             pcmpeqb %xmm0,%xmm4
   8067e:       66 0f d7 d4             pmovmskb %xmm4,%edx
   80682:       85 d2                   test   %edx,%edx
   80684:       74 04                   je     8068a <strlen@@GLIBC_2.2.5+0x3a>
   80686:       0f bc c2                bsf    %edx,%eax
   80689:       c3                      retq
   8068a:       48 83 e0 f0             and    $0xfffffffffffffff0,%rax
   8068e:       66 0f 74 48 10          pcmpeqb 0x10(%rax),%xmm1
   80693:       66 0f 74 50 20          pcmpeqb 0x20(%rax),%xmm2
   80698:       66 0f 74 58 30          pcmpeqb 0x30(%rax),%xmm3
   8069d:       66 0f d7 d1             pmovmskb %xmm1,%edx
   806a1:       66 44 0f d7 c2          pmovmskb %xmm2,%r8d
   806a6:       66 0f d7 cb             pmovmskb %xmm3,%ecx
   806aa:       48 c1 e2 10             shl    $0x10,%rdx
   ...
   ...

Как видите, даже такая простая функция на самом деле кажется невозможной для понимания в джунглях сложных инструкций из-за многочисленных оптимизаций и ручной настройки, применяемых авторы glib c по годам.

2 голосов
/ 07 февраля 2020

То, что вы предлагаете, является плохой идеей, потому что код рабочей библиотеки скомпилирован с оптимизацией. Оптимизированный код не является невозможным для понимания, но он может быть сложным. Почему? gcc, например, часто выбирает векторные инструкции, которые вы, вероятно, не хотите или вам необходимо узнать. Это развернет простые циклы в длинные участки повторяющегося кода. Он будет переставлять инструкции в не интуитивном порядке, чтобы поддерживать конвейер процессора заполненным. Когда вы учитесь, это источники путаницы.

То, что вы можете сделать, чтобы учиться, это скомпилировать C с легкой оптимизацией.

Godbot Compiler Explorer хорош для этого. Дайте ему небольшие фрагменты кода и посмотрите, что делают разные компиляторы с разными уровнями оптимизации. Ссылка выше показывает strlen. Вот a strcpy. Вот тот, которого нет в стандартной библиотеке вообще . Он перемещает указатель на char либо до конца строки, либо до первого появления символа разделителя. Т.е. это простой анализатор строк.

0 голосов
/ 19 февраля 2020

Generi c метод:

  1. find package, для Debian-like Linux:
$ apt-file search string.h

Конечно, это glib c .

получить исходный код, например, glib c 2.31 и скомпилировать его:
$ ./configure --prefix=/usr --enable-kernel=4.0.0 --disable-profile --with-gnu-ld --enable-stack-protector=strong
$ make
реализации функций обычно имеют одинаковые имена, поэтому найдите источник:
$ find . -type f -name "strstr.c"

это strings / strstr. c

дизассемблировать скомпилированную версию по умолчанию:
$ objdump -d string/strstr.o > strstr1.asm
сделать новую версию с настроенной оптимизацией: удалить объектный файл:
$ rm string/strstr.o`

римейк с выводом команд:

$ make V=1 &>strstrr.txt
  • это для "bash ", в противном случае используйте команду" script "

получить команду g cc из strstrr.txt, измените ее по своему усмотрению (оптимизация, тип процессора ...), например, измените -O2 на O10, и запустите:

$ cd strings
$ gcc ../sysdeps/x86_64/multiarch/strstr.c -c -std=gnu11 -fgnu89-inline  -g -O10 -Wall -Wwrite-strings -Wundef -Werror -fmerge-all-constants -frounding-math -fstack-protector-strong -Wstrict-prototypes -Wold-style-definition -fmath-errno      -ftls-model=initial-exec      -I../include -I/home/yury/LFSC/cross1/src/bglibcn/string  -I/home/yury/LFSC/cross1/src/bglibcn  -I../sysdeps/unix/sysv/linux/x86_64/64  -I../sysdeps/unix/sysv/linux/x86_64  -I../sysdeps/unix/sysv/linux/x86/include -I../sysdeps/unix/sysv/linux/x86  -I../sysdeps/x86/nptl  -I../sysdeps/unix/sysv/linux/wordsize-64  -I../sysdeps/x86_64/nptl  -I../sysdeps/unix/sysv/linux/include -I../sysdeps/unix/sysv/linux  -I../sysdeps/nptl  -I../sysdeps/pthread  -I../sysdeps/gnu  -I../sysdeps/unix/inet  -I../sysdeps/unix/sysv  -I../sysdeps/unix/x86_64  -I../sysdeps/unix  -I../sysdeps/posix  -I../sysdeps/x86_64/64  -I../sysdeps/x86_64/fpu/multiarch  -I../sysdeps/x86_64/fpu  -I../sysdeps/x86/fpu/include -I../sysdeps/x86/fpu  -I../sysdeps/x86_64/multiarch  -I../sysdeps/x86_64  -I../sysdeps/x86  -I../sysdeps/ieee754/float128  -I../sysdeps/ieee754/ldbl-96/include -I../sysdeps/ieee754/ldbl-96  -I../sysdeps/ieee754/dbl-64/wordsize-64  -I../sysdeps/ieee754/dbl-64  -I../sysdeps/ieee754/flt-32  -I../sysdeps/wordsize-64  -I../sysdeps/ieee754  -I../sysdeps/generic  -I.. -I../libio -I.   -D_LIBC_REENTRANT -include /home/yury/LFSC/cross1/src/bglibcn/libc-modules.h -DMODULE_NAME=libc -include ../include/libc-symbols.h       -DTOP_NAMESPACE=glibc -o /home/yury/LFSC/cross1/src/bglibcn/string/strstr.o -MD -MP -MF /home/yury/LFSC/cross1/src/bglibcn/string/strstr.o.dt -MT /home/yury/LFSC/cross1/src/bglibcn/string/strstr.o
$ objdump -d string/strstr.o > strstr2.asm

код будет другим:

$ diff strstr1.asm strstr2.asm

Таким образом, вы можете скопировать и вставить нужный код в вашу программу на ассемблере, чтобы сэкономить ваше время.

...