Moin moin und Willkommen an Board!
Die Aufrufkonventionen stehen natürlich einmal in den entsprechenden Dokumentationen (ELF-Spezifikation, Microsoft Visual C++-Dokumentation, ...), aber das Wichtigste findest du auch in dem
Wikipedia-Artikel dazu.
zu 1.) Das rundet den Wert in ESP auf ein Vielfaches von 16 ab. Kurzes googeln ergibt, dass das ermöglichen soll, Parameter für SIMD-Instruktionen (SSE, etc.) auf den Stack legen zu können, weil diese auf 16-Bytes ausgerichtet sein müssen. Glaub ich jetzt einfach mal so.
zu 2.) Damit werden 16 Bytes (16 = 0x10) auf dem Stack reserviert. Die können für lokale und temporäre Variablen verwendet werden, oder wie in diesem Fall teilweise durch die Parameter.
mov DWORD PTR [esp+0x4],0x5 und
mov DWORD PTR [esp],0x3 schreiben die beiden Parameter 3 und 5 in diesen Bereich. Das ist wieder ein Optimierungsversuch des Compilers, der sich die push-Befehle sparen will. Die Code ist fast äquivalent zu:
push irgendwas ; damit genau 16 bytes gepusht werden
push irgendwas ; damit genau 16 bytes gepusht werden
push 5
push 3
call _fn
Du siehst, dass in der Compilerausgabe an die Adressen ESP+4 und ESP+0 geschrieben wird. Der folgende CALL-Opcode verringert ESP um 4 und schreibt die Rücksprungadresse auf den Stack. Die Parameter liegen also nun bei ESP+8 bzw. ESP+4. Die Funktion _fn legt nun EBP auf den Stack, was ESP wieder um 4 verringert. Dadurch kann auf die Parameter über ESP+12 bzw. ESP+8 zugegriffen werden. (Das macht der Code auch, aber er kopiert vorher den Wert von ESP nach EBP und greift über EBP darauf zu.)
zu 3.) Das in den Spitzenklammern in der Ausgabe des Disassemblers ist kein Teil des Assemblercodes mehr sondern Anmerkungen. Die stehen in der Regel hinter Zahlen, die Adressen angeben. Also 00000000 <_fn>: heißt, dass an der Adresse 0 die Funktion _fn liegt. Der Call-Opcode hat auch nur ein Argument und call 0 <_fn> heißt, dass der Code an Adresse 0 aufgerufen wird. Das <_fn> dahinter bedeutet, dass der Disassembler festgestellt hat, dass dort die Funktion _fn liegt. Lass dich nicht von den Adressen verwirren: Da du eine Objekt-Datei disassembliert hast, sind die Funktionen noch nicht an ihre endgültige Adresse gelinkt und deswegen beginnt alles bei 0. Der Disassembler schreibt übrigens nicht
call _fn (oder
call print_string), sondern call 0 <_fn>, weil im Maschinencode nur noch call 0 steht, und er das mit dem _fn selbst herausgefunden hat.
Der Aufruf
call 1b <_main+0xe> hat übrigens irgendwas damit zu tun, wie Windowsprogramme aussehen müssen, und der Linker wird dort später eine korrekte Adresse einsetzen. Irgendeine Libraryfunktion muss da aufgerufen werden. Den Befehl kannst du ignorieren.