Pour un projet scolaire, je dois faire une grande quantité de manipulation de chaînes en assemblage. Comme c'est pénible à faire, j'essayais de trouver des moyens innovants d'utiliser des opérations de cordes déjà programmées. Mon idée est de compiler et de vider l'assembly de la bibliothèque string.h
dans c. Ensuite, je copierais coller l'assembly sauvegardé dans mon programme. Après avoir déterminé l'emplacement mémoire de chaque fonction et ses paramètres, je pense que je pourrais essentiellement appeler la fonction.
Pour vider l'assemblage, j'ai d'abord écrit un programme qui comprenait les bibliothèques que je voulais:
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
Ensuite, j'ai compilé et vidé l'assembly en utilisant
gcc -o lib lib.c objdump -d *o
Quand j'ai regardé le résultat, j'ai remarqué qu'il n'incluait aucun assemblage pour les bibliothèques. Je suppose qu'il y a soit une optimisation du compilateur qui n'inclut pas les fonctions inutilisées, soit la sortie de la bibliothèque est masquée lorsque j'utilise objdump
:
#include <stdio.h> #include <string.h> int main() { return 0; }
En remarque, j'utilise OSX Catalina, mais je peux passer à Ubuntu ou à un autre système d'exploitation si cela serait plus facile.
Comment puis-je faire le vidage de l'asm pour la string.h bibliothèque?
3 Réponses :
Tout d'abord, permettez-moi de commencer par dire qu'il s'agit vraiment d'un problème XY .
Mon idée est de compiler et de vider l'assembly de la bibliothèque string.h dans c. Ensuite, je copierais coller l'assembly sauvegardé dans mon programme.
Vous ne devriez pas faire cela. La bibliothèque standard a des fonctions très méticuleusement optimisées qui doivent être traitées avec soin et qui sont très , très compliquées. En d'autres termes, ils sont fondamentalement inutiles à des fins éducatives si vous apprenez l'assemblage.
Vous devriez vraiment simplement écrire votre implémentation préférée en C, puis la compiler.
Un fichier d'en-tête (tel que string.h
) ne contient généralement pas de définitions de fonction. Il ne contient que leur déclaration. Les fonctions réelles sont en fait déjà compilées dans un objet de bibliothèque dynamique qui est installé dans votre système (c'est-à-dire la bibliothèque elle-même).
Lorsque vous compilez un programme, le compilateur lie automatiquement à la bibliothèque C standard. Selon cette réponse , sous OS X, la bibliothèque standard doit se trouver à /usr/lib/libSystem.B .dylib
. Sur Ubuntu, il s'agit généralement de /lib/x86_64-linux-gnu/libc.so.6
. Ce qui suit s'applique aux deux plates-formes sans problème.
Si vous voulez jeter un coup d'œil au désassemblage d'une fonction de bibliothèque particulière, vous pouvez exécuter objdump
sur la bibliothèque en le redirigeant vers less , puis recherchez le nom de la fonction:
$ 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 ... ...
À l'intérieur de less
, vous pouvez rechercher en tapant /
suivi du nom de la fonction, puis appuyez sur Entrée et utilisez n ou N pour parcourir les correspondances. p>
Alternativement, vous pouvez vider la sortie de objdump
dans un fichier et l'inspecter avec un éditeur de texte:
$ 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
Le problème lorsque faire ce genre de chose est que la bibliothèque standard a beaucoup de noms différents et plus compliqués pour les fonctions standard que ceux que vous voyez dans string.h
. En interne, les symboles utilisés sont différents. Par exemple, sous Linux, lorsque vous utilisez printf
, le symbole correspondant dans la libc est en fait __printf
. Voir ici par exemple.
Vous pouvez trouver le vrai nom de symbole d'une fonction de bibliothèque standard en compilant un programme qui l'utilise et en regardant le code désassemblé, par exemple:
#include <string.h> #include <stdio.h> int main(void) { char s[100]; scanf("%99s", s); size_t len = strlen(s); return 0; }
Ensuite, lancez:
$ objdump -d /usr/lib/libSystem.B.dylib > libSystem.disasm
Et vous pouvez voir que dans mon cas, scanf
est en fait __isoc99_scanf
, tandis que strlen
est inchangé.
Je peux alors rechercher le désassemblage de strlen
, qui sur mon système (Ubuntu) est le suivant:
$ objdump -d /usr/lib/libSystem.B.dylib | less
Comme vous pouvez le voir, même une fonction aussi simple est en fait une jungle apparemment impossible à comprendre d'instructions compliquées, en raison des nombreuses optimisations et réglages manuels appliqués par les auteurs de la glibc au fil des ans. p>
«Impossible de comprendre la jungle des instructions compliquées» est une exagération. De nombreuses personnes font de la rétro-ingénierie de leur vie des logiciels malveillants, par exemple, ce qui est plus compliqué que strlen, car vous connaissez son objectif à l'avance. Bien sûr, je suis tout à fait d'accord que tenter de faire de l'ingénierie inverse du code déroulé en boucle pour gagner du temps sur un devoir scolaire est une très mauvaise idée.
@Gene: ou si vous voulez comprendre les fonctions de la glibc, lisez le source asm commenté manuscrit! Il utilise certaines macros mais ressemble autrement à la syntaxe AT&T normale. par exemple. la version SSE2 code.woboq.org/userspace/glibc/sysdeps /x86_64/strlen.S.html (Voir aussi Pourquoi le strlen de la glibc doit-il être si compliqué à exécuter rapidement? pour certains informations sur les versions de secours C par rapport aux versions asm manuscrites, avec des liens.) De plus, le memcpy / memset de la glibc est sympa, utilisant 2 magasins qui se chevauchent pour les petits tampons a>
@Gene Je sais, je fais du RE pour m'amuser moi-même, je parlais juste d'un point de vue novice puisque OP apprend l'assemblage. J'aurais peut-être exagéré avec l'hyperbole là-bas: ') Bien que les logiciels malveillants n'utilisent généralement pas tous ces opcodes optimisés, ils sont plus auto-modifiants et masqués que des instructions absurdes.
Ce que vous proposez est une mauvaise idée car le code de la bibliothèque de production est compilé avec une optimisation augmentée. Le code optimisé n'est pas impossible à comprendre, mais cela peut être compliqué. Pourquoi? gcc
, par exemple, choisira souvent des instructions vectorielles que vous ne voulez probablement pas ou dont vous n'avez probablement pas besoin d'apprendre. Il déroulera de simples boucles en de longues séquences de code répétitif. Il réorganisera les instructions dans des ordres non intuitifs pour garder le pipeline du processeur plein. Lorsque vous apprenez, ce sont des sources de confusion.
Ce que vous pouvez faire de manière productive pour apprendre, c'est compiler C avec une optimisation légère.
L ' Godbot Compiler Explorer est bien pour cela. Donnez-lui de petits fragments de code et voyez ce que les différents compilateurs font avec différents niveaux d'optimisation. Le lien ci-dessus montre un strlen
. Voici un strcpy
. En voici un qui ne fait pas du tout partie de la bibliothèque standard . Il fait avancer un pointeur sur char vers la fin de la chaîne ou la première apparition d'un caractère séparateur. C'est-à-dire qu'il s'agit d'un simple analyseur de chaîne.
Méthode générique:
$ diff strstr1.asm strstr2.asm
Bien sûr, c'est la glibc.
$ 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
$ make V=1 &>strstrr.txt
c'est strings / strstr.c
$ rm string/strstr.o`
$ objdump -d string/strstr.o > strstr1.asm
remake avec la sortie des commandes:
$ find . -type f -name "strstr.c"
récupérez la commande gcc de strstrr.txt, modifiez-la comme vous le souhaitez (optimisation, type de processeur ...), par exemple changez -O2 en O10, et exécutez:
$ ./configure --prefix=/usr --enable-kernel=4.0.0 --disable-profile --with-gnu-ld --enable-stack-protector=strong $ make
le code sera différent:
$ apt-file search string.h
Donc, vous pouvez copier -Coller le code que vous voulez dans votre programme assembleur pour gagner du temps.
La plupart des fonctions de chaîne de la glibc ont des implémentations asm x86. La compilation des solutions de secours génériques peut vous donner un certain asm pour une version trop compliquée qui fait probablement 4 octets à la fois avec un bithack pour vérifier une éventuelle fin de chaîne. Ou si vous trouvez code.woboq.org/userspace/ glibc / sysdeps / x86_64 / multiarch /… alors vous regardez le code de répartition CPU d'exécution de l'éditeur de liens dynamique qui sélectionne l'implémentation réelle à résoudre, en fonction de la machine hôte. Si vous souhaitez copier asm depuis la glibc, vous pouvez aussi simplement récupérer la source x86_64 / multiarch / strstr-sse2-unaligned.S
glibc est une librairie multiarch (sans mobile): `` `` $ find. -type f -name "strstr.c" ./sysdeps/x86_64/multiarch/strstr.c ./sysdeps/powerpc/powerpc64/multiarch/strstr.c ./sysdeps/s390/strstr.c ./string/strstr.c `` Une autre implémentation multiarchive de la bibliothèque C standard est en.wikipedia.org/wiki/Bionic_ ( logiciel) Plate-forme: x86, x86-64, ARM, ARM64, MIPS, MI ...
glibc «multiarch» ne veut pas dire «portable», cela signifie qu'il gère la distribution d'exécution pour quelques variantes de CPU x86-64, ou quelques variantes de PowerPC, où le meilleur choix d'implémentation est sélectionné au moment de la liaison dynamique. L'implémentation de secours C portable de Glibc est code.woboq .org / userspace / glibc / string / strstr.c.html . (Ce fichier est #include<>
édité par le fichier x86 multiarch .c comme solution de secours pour les processeurs sans chargements / magasins non alignés SSE2 efficaces. D'autres fonctions comme strchr
ne le font pas avoir une solution de secours C, toujours une version asm.)
Le simple fait d'inclure les fichiers .h n'ajoutera aucune fonction au programme à moins que vous ne les appeliez réellement. L'appel de fonctions de bibliothèque n'ajoute pas non plus l'assembly à votre programme, il est simplement lié à l'assembly dans le fichier de bibliothèque partagé.
Notez que gcc a des fonctions intégrées pour certaines fonctions de chaîne, donc avec l'optimisation activée, vous pouvez très bien insérer du code (si vous utilisez la fonction qui est). Si vous êtes intéressé par la version de la bibliothèque, vous pouvez bien sûr démonter la bibliothèque elle-même sans rien compiler.
(Un point pour sortir des sentiers battus. Sauf si vous étiez censé l'écrire vous-même pour cet exercice scolaire ...) Il devrait être possible de vider également le contenu des bibliothèques standard.
Au lieu de l'inclure, vous pouvez simplement regarder la source tout de suite et essayer de la compiler: Linux string.c
@Frederik ce lien n'a rien à voir avec la bibliothèque C standard.
hah je vois .. et par "innovant" vous voulez vraiment dire "contourner la mission"
Le professeur a dit qu'il pensait que c'était difficile et a dit de voir si je pouvais le faire fonctionner
string.h
est un en-tête, pas une bibliothèque. Il ne contient aucun code pour aucune fonction. Le code est dans la libc liée dynamiquement.@Jester Serait-il plus facile de démonter un binaire déjà compilé au lieu de le compiler moi-même et de faire un objdump?
Vous démontez la bibliothèque. Voir la réponse de Marco