Wie Programmstart-Argumente in Assembler auslesen?

G

gu4rdi4n

Freak
hi,
ich bin grade dabei Asembler zu lernen, und hätte da mal eine Frage.
Wie kann ich die Argumente, welche beim Programmstart unter Linux gegeben sind, in Asembler auslesen?

Wenn ich zum Beispeil ein Programm mit "./meinProgramm 50 100 150" starten würde, wie könnte ich die hinteren werte(50, 100 und 150) in Asembler unter Linux auslesen?


MfG
GU4RDI4N
 
Hi,

ich bin selber nicht so der Assembler Crack, aber ich dachte mir, dass man das eigentlich rausbekommen koennte, indem man sich mal die Assembler Anweisungen von einem ganz simplen C Programm anschaut:

Code:
#include <stdio.h>

int main(int argc, char * argv [])
{
  if (argc < 2)
    printf("file name: %s\n", argv[0]);
  else
    printf("first cl argument: %s\n", argv[1]);
}

Ich gehe jetzt mal davon aus, dass du dich mit C schon halbwegs auskennst und weisst was da passiert.

Das habe ich jetzt mit "gcc -S ./cl-args.c -fverbose-asm" in asm code umgewandelt (cl-args.s).

Der relevante asm code sieht wie folgt aus:

Code:
	.file	"cl-args.c"
	.section	.rodata
.LC0:
	.string	"file name: %s\n"
.LC1:
	.string	"first cl argument: %s\n"
	.text
.globl main
	.type	main, @function
main:
.LFB2:
	pushq	%rbp	#
.LCFI0:
	movq	%rsp, %rbp	#,
.LCFI1:
	subq	$16, %rsp	#,
.LCFI2:
	movl	%edi, -4(%rbp)	# argc, argc
	movq	%rsi, -16(%rbp)	# argv, argv
	cmpl	$1, -4(%rbp)	#, argc
	jg	.L2	#,
	movq	-16(%rbp), %rax	# argv, argv
	movq	(%rax), %rsi	#* argv, D.2135
	movl	$.LC0, %edi	#,
	movl	$0, %eax	#,
	call	printf	#
	jmp	.L6	#
.L2:
	movq	-16(%rbp), %rax	# argv, D.2136
	addq	$8, %rax	#, D.2136
	movq	(%rax), %rsi	#* D.2136, D.2137
	movl	$.LC1, %edi	#,
	movl	$0, %eax	#,
	call	printf	#
.L6:
	leave
	ret

In der generierten Datei steht noch mehr, aber ich denke das ist der wichtige Teil.
So wie es aussieht, steht argc direkt nach Programmaufruf in %edi und der argv Pointer in %rsi (auf 32 bit systemen ist das wahrscheinlich einfach %esi).

Ich weiss jetzt nicht genau wie weit du so mit deinem Assembler Wissen bist und wie klar obiger Code fuer dich ist. Insbesondere musst du 64 bit Assembly und die AT&T Syntax verstehen um mit dem Code was anfangen zu koennen.
Sag Bescheid falls etwas Unklar ist, aber ich finde den Code eigentlich recht aussagekraeftig.

mfg,
bytepool
 
Hi,

man sollte Assembler Code doch immer direkt vernuenftig kommentieren. Als ich das schrieb, war mir klar was da geschieht, jetzt ein 3/4 Jahr spaeter finde ich das nicht mehr so klar.

Ich glaube ich mache mir jetzt doch nochmal die Muehe das Stueck fuer Stueck durch zu gehen, und zu kommentieren, und wenn es nur fuer mich selber ist. ;)

Code:
	.file	"cl-args.c"
	.section	.rodata
.LC0:
	.string	"file name: %s\n"
.LC1:
	.string	"first cl argument: %s\n"
	.text
.globl main
	.type	main, @function
main:
.LFB2:
	pushq	%rbp	        # sichere den base pointer auf dem Stack
.LCFI0:
	movq	%rsp, %rbp	# schreibe den alten stack pointer in den base pointer
.LCFI1:
	subq	$16, %rsp	# reserviere 16 byte auf dem Stack
.LCFI2:
	movl	%edi, -4(%rbp)	# schreibe argc auf den Stack, in den dafuer reservierten Bereich
	movq	%rsi, -16(%rbp)	# schreibe argv auf den Stack
	cmpl	$1, -4(%rbp)	# compare(1, argc)
	jg	.L2	        # if (argc > 1): goto .L2
	movq	-16(%rbp), %rax	# schreibe argv in Register rax
	movq	(%rax), %rsi	# schreibe argv[0] in rsi
	movl	$.LC0, %edi	# schreibe die Adresse von string .LC0 in edi
	movl	$0, %eax	# eax = 0
	call	printf	        # rufe printf auf
	jmp	.L6	        # goto .L6
.L2:
	movq	-16(%rbp), %rax	# schreibe argv in Register rax
	addq	$8, %rax	# fuege 8 byte zu rax hinzu (rax zeigt nun auf argv[1])
	movq	(%rax), %rsi	# schreibe argv[1] in rsi
	movl	$.LC1, %edi	# schreibe die Adresse von string .LC1 in edi
	movl	$0, %eax	# eax = 0
	call	printf	        # rufe printf auf
.L6:
	leave                   # stelle alte Register Zustaende wieder her
	ret                     # return

Sollte ich etwas falsch kommentiert haben, duerft ihr mich gerne korrigieren. ;)

mfg,
bytepool
 
Zuletzt bearbeitet:
Nur aus Interesse, wozu brauchst den Assembler Code?
 
Hi,

Nur aus Interesse, wozu brauchst den Assembler Code?
wen meinst du jetzt, den TE oder mich? Ich brauche diesen konkreten Code gar nicht, aber ich mag es nicht, wenn ich irgendwo Code veroeffentliche, den ich dann ein Jahr spaeter selber nicht mehr verstehe. Daher nun nochmal die ausfuehrlicheren Kommentare. ;)

Wenn deine Frage eher darauf abzielt, wofuer man Assembler heutzutage ueberhaupt noch braucht, waere die Antwort dass ich zu den Leuten gehoere, die glauben dass low-level Verstaendnis auch auf hoeheren Abstraktionsebenen sehr hilfreich ist. Aber ich kaeme sicherlich auch nicht auf die Idee ein ganzes Programm in Assembler zu schreiben.

Doch wenn du dich zum Beispiel mit security bugs beschaeftigst, kommst du um Assembler nicht herum. D.h. auch wenn du "nur" sichere C Programme schreiben willst, hilft ein tieferes Verstaendnis, was da bei Ausfuehrung eigentlich genau passiert. Aber auch da gibt es unterschiedliche Meinungen zu.

mfg,
bytepool
 
Naja der Thread ist schon etwas älter, meinte dazu den TE.
 
Vielen Dank bytepool, das verbesserte Stück Code ist nicht nur für dich. Und außerdem per google von jedem Suchenden auffindbar. :))

Allerdings habe ich dem noch ein paar Sachen hinzuzufügen, nachdem ich mich heute etwas mit der Sache beschäftigt habe.

Der von dir erzeugte und kommentierte Code liefert keinerlei Argumente, wenn man ihn ausprobiert. Auch argc ist nach deiner "Methode" auch stets 0.

Jetzt mein Erklärungsversuch: Wie ich auch erst heute gelernt habe, wird ein Programm normalerweise so erstellt, dass nicht nur der eigene Code im Binary landet, sondern allerhand libc-code dazugelinkt wird, egal ob man den braucht oder nicht...

Code:
int main() {
  return 0;
}

z.B. führt zu einem recht großen Binary und ein ldd darauf führt bei mir zu

user@rechner:~$ ldd ./a.out
linux-vdso.so.1 => (0x00007ffffd1ff000)
libc.so.6 => /lib/libc.so.6 (0x00007f6cf4b30000)
/lib64/ld-linux-x86-64.so.2 (0x00007f6cf4e92000)

Deswegen hört man von Neulingen manchmal auch Hilferufe wegen Fehlermeldungen ala "cannot find entry symbol _start" bei einem Hello-Wolrd-Programm, obwohl das natürliche keine "_start"-Funktion enthält.

Außerdem fand ich es vor einiger Zeit sehr interessant, dass ein leeres
int main() {return 0;}
zu einem Segmentation fault führt. Was erstmal sehr erstaunlich zu sein scheint. 8o


:oldman Anscheinend wird das eigene Programm von anderem Code gekapselt. Und zwar libc-code, der z.B. in einer "_start"-Funktion steht. Kann auch andere Namen haben, k.A... Damit fängt das fertige Programm wohl an und nicht gleich mit meiner eigenen main-Funktion. main() wird erst von diesem Kapselcode irgendwo aufgerufen und springt am Ende, nämlich beim "return 0;" wieder zu diesem Kapselcode zurück. Und eben nicht ins Betriebssystem. Der Kapselcode kann dann noch aufräumen und "richtig" beenden. Das würde auch den Segfault in dem leeren c-Programm erklären, da beim aufruf von "return 0;" ja keine gültige Rücksprungadresse auf dem Stack liegt und der Sprung "irgendwohin" geht, aber nicht dahin, wo es erlaubt ist und deswegen ein segfault ausgelöst wird.

Das rauszufinden hat mich viel viel Zeit gekostet und ich wäre dankbar wenn mir das ein Fachkundiger bestätigen könnte :)


Meine Frage wäre jetzt, was genau macht der Code alles? Das weiss ich bis jetzt nämlich nicht und in diesem "wusligen" glibc-Quellcode seh ich auch nicht wirklich durch, leider!?!

Im libc-code, der nach dem "return 0;" ausgeführt wird, wird sys_exit, ein Systemaufruf zum beenden von Programmen, aufgerufen, das ist klar und am Anfang des libc-codes, also noch vor Aufrufen der main-funktion müssen die Kommandozeilenargumente irgendwo her geholt werden und gemäß der AMD64 (aka x86-64) ABI calling-conventions verpackt werden. (siehe http://en.wikipedia.org/wiki/X86_calling_conventions#AMD64_ABI_convention)

Da sieht man auch gleich, dass die von bytepool gefundenen argc und argv parameter, die er in edi und rsi "gefunden" hat, demnach auch genau dort zu erwarten sind, weil eben dort die Funktionsparameter in C auf x86-64-Plattform landen und main(int argc, char **argv) auch nur eine normale Funktion ist. ;)


Unter http://fixunix.com/unix/531186-how-grab-command-line-args-without-libc.html und http://asm.sourceforge.net/articles/startup.html findet man, dass die Argumente auf dem stack liegen sollen...bei 32-bit.

Frage nun: Kann das wer bestätigen? Ist das auch unter 64-bit so?? :help:



Ist leider etwas länglich geworden der Beitrag, aber da das recht komplex ist, ist es vielleicht besser zu viel zu schreiben, als dass irgendwer mich nicht versteht. ^^

Hab mich sogar für diesen Beitrag extra hier im Forum angemeldet.

Und wegen der Frage von Akendo...warum suche (auch) ich nach sowas...? xD

Erstens schonmal aus Interesse, wie und was da alles passiert beim Kompilieren bis zum Ausführen.
Solches Wissen bringt meiner Erfahrung nach auch viele Vorteile beim Lösen von Problemen, die man so beim Programmieren und Arbeiten oft hat.

Außerdem kann ich es persönlich nicht leiden, wenn meine eigenen Programme mit unnötigem Code verseucht und aufgeblasen werden und ich unnötige Bibliotheksabhängigkeiten habe. bloat gibt es schon genug!

Ein Hello-World-programm in C kann man zB auch so kompilieren, dass kein 8kb-binary entsteht :D
Und die glibc braucht man dafür eigentlich auch überhaupt nicht, weder dynamisch noch statisch dazugelinkt, aber das ist ein anderes Thema xD
 

Ähnliche Themen

Kernel Kaltstart / reboot?

openn SuSE 13.1 - 64-BIt erlaubt nicht mehr als 20GB für /root

Neues debootstraped Linux mit chroot und kexec starten

Samba killt Server bei Datei-Upload, wie tracen?

Heimserver Konfiguration für Ubuntu Server?!

Zurück
Oben