Assembler, Mnemonics, Opcodes und Architekturen.
-
Ich wollte eigentlich mal einen ganzen Haufen Fragen stellen, da ich mich in letzter Zeit damit vermehrt beschäftige (die Fragen gelten übrigens immer für Windows, meinethalten XP, und dem Linuxkernel):
Nehmen wir mal an, ich würde in C oder C++ eine Anwendung schreiben, das Ganze noch optimieren, kompilieren und linken. Heraus kommt eine Anwendungsdatei - allgemein auch als *.exe bezeichnet. In dieser *.exe steht nun der ganze Krull drin, den das Betriebssystem so braucht, damit es die *.exe ausführen kann.
1. Frage: Dieser Maschinencode ist binär, ich weiß. Wenn ich das Ganze aber mit einem Texteditor betrachte, der die Werte als Maschinenbefehle ansieht, zeigt dieser mir auch Mnemonics an, Labels, die genauer beschreiben, was der Befehl tut. Obwohl diese Mnemonics ein bisschen mit Assembler zu tun haben (in Assembler-Dialekten werden allerdinds manchmal Anweisungen geschrieben, die so im Maschinencode nicht auftauchen) nennt man sie offiziel (glaube ich zumindest, deshalb frage ich) Opcodes, weil es ja kein Assembler mehr ist, sondern richtige Maschinensprache, richtig?
2. Wie diese Opcodes interpretiert werden, ist Sache des Prozessors und des Betriebssystems (?). Was passiert, wenn das OS auf einen Befehl trifft, den er nicht kennt, standardmäßig? Oder macht das überhaupt nichts aus - beim Prozessor weiß ich, dass er den Befehl einfach ignorieren oder das Programm direkt terminieren kann?
3. Sprachen, die nicht so maschinennah sind wie C oder C++, sind für gewöhnlich größer. Ich habe einmal gelesen, dass jemand hier für ein VBA-Programm (Gab einfach nur "Hallo, Welt!" aus) 25 KB brauchte, während ich hier ein umfangreiches Konsolenprogramm habe, welches mit vielen Daten und Backups hantiert und dabei nicht mehr als 31 KB verbraucht. Meine Frage: warum sind VBA-exen so groß (habe selbst noch nicht mit Visual Basic gearbeitet)? Ist dort ein ähnlicher Code wie der IL-Code aus C, weil das ganze interpretiert werden muss? Oder stecken dort nur Optimierungen für die gängigsten Architekturen, welche die Exe so groß machen?
4. Eine *.exe besitzt mehrere Register, in die sie "geladen" wird - eigentlich bildet sie sich das aber bloß ein. Denn durch den virtuellen Adressraum glaubt jede *.exe, sie wäre allein im Speicher, und das OS lädt alle Daten so, dass die *.exe das auch weiterhin glaubt. Aber in jeder *.exe, die ich bis heute gesehen habe, blitzte mich ein "MZ" als erste Zeichen an. Ich weiß, dass es so etwas wie einen "exen-Header" gibt, in dem einige Informationen über die Anwendung stehen. Aber wie sind diese Header definiert - wo steht was (Einsprungspunkt, Prüfsumme, etc ...)? Und was machen diese "MZ"?
5. (nur Windows) Im Header muss eigentlich auch stehen, für welche Architektur das Programm kompiliert wurde. Ich vermute, dass, wenn die entsprechende Information gesetzt wurde und das Programm auf einer x64-Plattform läuft, automatisch die Bibliotheken für den WOW64-Emulator geladen werden, oder?
-
Die Befehle (Opcodes oder wie du sie nennen willst) gelten nicht für das Betriebssystem, sondern für den Prozessor, der in deinem PC verbaut ist. Und wenn der etwas vorgesetzt bekommt, was der nicht versteht, dann macht er damit Müll. Dieser Prozessor besitzt auch die Register (eine begrenzte Menge an Speicher, auf die er direkt zugreifen kann).
@4: dieses "MZ" ist eine Magic Number und sollen einfach nur signalisieren, daß du dort eine EXE vor dir hast.
-
CStoll schrieb:
Die Befehle (Opcodes oder wie du sie nennen willst) gelten nicht für das Betriebssystem, sondern für den Prozessor, der in deinem PC verbaut ist. Und wenn der etwas vorgesetzt bekommt, was der nicht versteht, dann macht er damit Müll. Dieser Prozessor besitzt auch die Register (eine begrenzte Menge an Speicher, auf die er direkt zugreifen kann).
Ja, die Register kenne ich auch. Unter MS-DOS (und auch andere DOS-Distibutionen?) und im Realmode hat man Werte in diese Register gelanden und dann Interrupts ausgeführt, die hardwarebasierende Funktionen auslösen - stimmt doch, oder?
CStoll schrieb:
@4: dieses "MZ" ist eine Magic Number und sollen einfach nur signalisieren, daß du dort eine EXE vor dir hast.
Waren vor dem *.exe-Format (ich sollte eher sagen, vor dem PE-Format) *.coms gebräuchlicher?
-
Wenn ich das Ganze aber mit einem Texteditor betrachte, der die Werte als Maschinenbefehle ansieht, zeigt dieser mir auch Mnemonics an, Labels, die genauer beschreiben, was der Befehl tut.
Du bekommst also wirklich Dinge angezeigt wie mov eax, 0x1337 etc.
Mit einem üblichen Texteditor?Oder nur so was wie GetModuleHandleA?
-
cooky451 schrieb:
Du bekommst also wirklich Dinge angezeigt wie mov eax, 0x1337 etc.
Mit einem üblichen Texteditor?Oder nur so was wie GetModuleHandleA?
Sagen wir mal so, ich verwende Texteditore, die diesen Feature unterstützen - schon mal was von HIEW gehört? Damit habe ich meinen ersten Spielstandseditor geschrieben - Mann, das waren noch Zeiten.
Jedenfalls, damit ist das möglich. Ich hätte auch IDA nehmen können, aber das ist nun kein Texteditor, oder?
-
Der aus dem Westen ... schrieb:
cooky451 schrieb:
Du bekommst also wirklich Dinge angezeigt wie mov eax, 0x1337 etc.
Mit einem üblichen Texteditor?Oder nur so was wie GetModuleHandleA?
Sagen wir mal so, ich verwende Texteditore, die diesen Feature unterstützen - schon mal was von HIEW gehört? Damit habe ich meinen ersten Spielstandseditor geschrieben - Mann, das waren noch Zeiten.
Jedenfalls, damit ist das möglich. Ich hätte auch IDA nehmen können, aber das ist nun kein Texteditor, oder?
Das heißt im Klartext, du hast das Programm deassembliert und wunderst dich nun, dass du Assembleranweisungen siehst?
-
Der aus dem Westen ... schrieb:
Ich hätte auch IDA nehmen können, aber das ist nun kein Texteditor, oder?
Das ist ein Debugger, und halt kein Texteditor. Ein Texteditor liest nur Text und da steht halt nichts von mov, add, jmp etc.
Deswegen war ich gerade etwas verwirrt.http://de.wikipedia.org/wiki/Ring_(CPU)
Könnte vielleicht ganz interessant sein, englischen Artikel nicht vergessen.Sprachen, die nicht so maschinennah sind wie C oder C++, sind für gewöhnlich größer.
Da habe ich gegenteilige Erfahrungen gemacht. Die 3 C# Programme die ich bis jetzt so produziert habe, waren alle erstaunlich klein. Aber ich lasse mich da auch gerne eines Besseren belehren. Es ist aber halt auch nicht ganz unwichtig, ob du die Laufzeitlib mit linkst oder nicht. Gerade bei "Hello, World" oder so dürfte das gravierende Unterschiede machen.
-
Zu 1)
Der Opcode ist einfach der vordere Teil eines Maschienenbefehls, der angibt um welche Operation es sich handelt. Ein Mnemonic ist einfach eine für den Menschen besser lesbare Darstellung des Opcodes. Da, wie du schon erkannt hast, das nicht immer 1:1 übersetzt wird, würde ich das nicht gleichsetzen.Zu 2)
Wie schon gesagt wurde, hat das Betriebssystem nichts mit der Codeausführung zu tun. Das BS läd das Programm in den Speicher und springt in den Code. Der Prozessor führt dann ausschließlich den Programmcode aus, bis aus dem Programm auf BS Funktionen zugegriffen wird, oder ein Interrupt auftritt. Bei unbekannten oder fehlerhaften Befehlen wird ebenfalls ein Interrupt (Je nach Architektur auch Exception genannt) ausgelöst und dementsprechend die Kontrolle an das BS übergeben, das dann in der Regel das Programm beendet. Damit hat der Prozessor dann aber wenig zu tun. Es gibt durchaus auch Exceptions die vom BS behandelt werden können und nicht zum beenden des Programms führen.Zu 3)
Es kommt da auch sehr stark darauf an wie groß die Standardbibliothek ist, und wieviel davon statisch mitgelinkt wird. Interpretierte und VM Sprachen sind da aber Sonderfälle, da bei denen die Standardbibliothek im Interpreter/VM stecken und nicht im "Programm".Zu 4)
http://www.lowlevel.eu/wiki/PE oder für mehr Details die offizielle Spec http://msdn.microsoft.com/en-us/windows/hardware/gg463119.aspx
-
Der aus dem Westen ... schrieb:
1. Frage: Dieser Maschinencode ist binär, ich weiß. Wenn ich das Ganze aber mit einem Texteditor betrachte, der die Werte als Maschinenbefehle ansieht, zeigt dieser mir auch Mnemonics an, Labels, die genauer beschreiben, was der Befehl tut. Obwohl diese Mnemonics ein bisschen mit Assembler zu tun haben (in Assembler-Dialekten werden allerdinds manchmal Anweisungen geschrieben, die so im Maschinencode nicht auftauchen) nennt man sie offiziel (glaube ich zumindest, deshalb frage ich) Opcodes, weil es ja kein Assembler mehr ist, sondern richtige Maschinensprache, richtig?
? nicht ganz klar. Aber Opcode ist einfach der Maschinencode, Assembler arbeiten dagegen mit Assemblerdirektiven, Macros und den ->Mnemonics, die wiederum nichts anderes sind, als eine wörtliche Entsprechung des Maschinenbefehls:
Opcode: 01d8 bzw. 0000 0001 1101 1000 bzw. Schaltergedöns
Mnemonic: add ax,bx
Frage: mit welchem Befehl kann man die beiden Register dx und cx zusammenzählen? a)obcode b)mnemonic2.
Der aus dem Westen ... schrieb:
Wie diese Opcodes interpretiert werden, ist Sache des Prozessors und des Betriebssystems (?). Was passiert, wenn das OS auf einen Befehl trifft, den er nicht kennt, standardmäßig?
siehe Tobiking2 und z.B.
http://www.htl-steyr.ac.at/~morg/pcinfo/hardware/interrupts/inte6kxc.htm3. siehe oben
Es werden in der Regel Bibliotheken für alles mögliche mitgeschleppt, und mehr Speicherzugriffbefehle und Stackbearbeitung als Register vs Register benutzt und wie man dem Code auch immer ansieht, jede Menge Redundanz usw.
Ganz lesenswert dazu:
http://www.mikrocontroller.net/articles/ASM_vs_C4.
Der aus dem Westen ... schrieb:
Und was machen diese "MZ"?
siehe u.a.:
http://de.wikipedia.org/wiki/Mark_Zbikowski
Hauptsächlich unterscheidt diese Signatur Exe von Com Programmen. Für Exe Programme übernimmt Dos die Reallokation, bei .com Programmen muss das von Hand gemacht werden.
Siehe auch noch hier:
http://www.lowlevel.eu/wiki/MZ_EXE5.
Der aus dem Westen ... schrieb:
(nur Windows) Im Header muss eigentlich auch stehen, für welche Architektur das Programm kompiliert wurde. Ich vermute, dass, wenn die entsprechende Information gesetzt wurde und das Programm auf einer x64-Plattform läuft, automatisch die Bibliotheken für den WOW64-Emulator geladen werden, oder?
Auch wieder nicht ganz klar. Der Windows kernel enthält viele Emulatoren, ist ein reines Multiemutalent. Für unterschiedliche Plattformen muss aber auch unterschiedlich compiliert werden. Bisher war es meist so, dass native Programme normalerweise nicht davon ausgehen, auf einer anderen Plattform ausgeführt zu werden.
-
ad 2) - Ungültige Opcodes (bzw. allgemein Befehle)
Die meisten Prozessoren, haben dafür sog. "fault handler". Nicht nur "invalid instruction" ist so ein fault, sondern auch die allseits beliebte "access violation" bzw. "segmentation violation" oder "division by zero" funktionieren über diesen Mechanismus.
Erkennt die CPU also einen ungültigen Befehl, dann führt sie stattdessen den "fault handler" für "invalid instruction" aus.
Passiert dabei wieder etwas (ein weiterer "fault"), dann nennt man das "double fault", und es wird der "double fault handler" ausgeführt.
Sollte das dann immer noch nicht hinhauen, dann nennt man das "triple fault". Die meisten CPUs quittieren das damit, dass sie einfach anhalten, oder sich komplett resetten.Über diese "fault handler" Funktionen kann das Betriebssystem kontrollieren, was bei einem "fault" passieren soll. Es kann also z.B. das verursachende Programm kontrolliert abgebrochen werden, ohne dass gleich das ganze OS hopps geht. Windows erlaubt es einem Programm sogar sich da einzuklinken, so dass es bestimmte Dinge selbst regeln kann, ohne dass das Programm dadurch gleich abgebrochen wird.
ad 3) - Die grossen VB Programme
Der Grund warum solche Programme so gross werden, ist die dazugelinkte Runtime. Auch ein Visual C++ Programm kann schnell mal ein paar zig oder hundert Kilobyte haben, wenn man die Runtime statisch dazulinkt, und ein paar Runtime-Funktionen verwendet die viele andere mit reinziehen.ad 4) - Das .exe Format
Wenn du wissen willst wie eine Windows .exe aufgebaut ist, dann guck bei Wikipedia nach, Stichwort PE Format (Portable Executable). Linux verwendet soweit ich weiss das ELF Format.Und eine ".exe" "besitzt" ganz sicher keine Register, und wird auch nicht "in Register geladen". Wenn du wissen willst was ein Register ist, empfehle ich ebenso Wikipedia.
ad 5) - Die .exe Header
Ja, in der Header steht auch drin ob ein Programm 32 Bit oder 64 Bit Code enthält. Das OS sieht dies beim Laden, und erzeugt dann entsprechend einen 32 Bit oder einen 64 Bit Prozess. Bzw. ein 32 Bit Windows sieht dann, dass es mit dieser .exe nix anfangen kann, und meldet entsprechend dass das Programm unter dieser Windows Version nicht verwendet werden kann.Der aus dem Westen ... schrieb:
Sagen wir mal so, ich verwende Texteditore, die diesen Feature unterstützen - schon mal was von HIEW gehört?
HIEW ist kein Texteditor.
-
SeppJ schrieb:
Das heißt im Klartext, du hast das Programm deassembliert und wunderst dich nun, dass du Assembleranweisungen siehst?
Nein, habe ich aber auch nicht gesagt ...
Sieh mal, wenn ich mit einem Assembler-Dialekt Programmiere (masm, tasm, fasm), kann ich spezielle, sehr maschinennahe Operationen ausführen, die jedoch kein Teil des Befehlssatzes eine x86 sind (zum Beispiel). Wenn ich mein Programm disassembliere, kann ich diesen Maschinencode sehen, aber die Anweisungen meines Assemblerprogramms (also das, was in meiner *.asm-Datei steht) stehen dort nur bedingt drin.
cooky451 schrieb:
Das ist ein Debugger, und halt kein Texteditor. Ein Texteditor liest nur Text und da steht halt nichts von mov, add, jmp etc.
Deswegen war ich gerade etwas verwirrt.Deshalb hatte ich ja auch HIEW im Hinterkopf.
Tobiking2 schrieb:
Zu 1)
Der Opcode ist einfach der vordere Teil eines Maschienenbefehls, der angibt um welche Operation es sich handelt. Ein Mnemonic ist einfach eine für den Menschen besser lesbare Darstellung des Opcodes. Da, wie du schon erkannt hast, das nicht immer 1:1 übersetzt wird, würde ich das nicht gleichsetzen.Ja, gut, war ein bisschen missverständlich ausgedrückt. Ich weiß, dass bei diesen Befehlen oft auch Indizes für Register oder Speicheradressen verwendet werden, sozusagen als Parameter.
nachtfeuer schrieb:
Frage: mit welchem Befehl kann man die beiden Register dx und cx zusammenzählen? a)obcode b)mnemonic
Ist das ein Test?
Mnemonic dürfte:
mov ax,dx add ax,cx
sein, oder? Binär weiß ich jetzt leider nicht ...
nachtfeuer schrieb:
Auch wieder nicht ganz klar. Der Windows kernel enthält viele Emulatoren, ist ein reines Multiemutalent. Für unterschiedliche Plattformen muss aber auch unterschiedlich compiliert werden. Bisher war es meist so, dass native Programme normalerweise nicht davon ausgehen, auf einer anderen Plattform ausgeführt zu werden.
Ja, das weiß ich. Und auch, wenn die Header für das OS kompatibel sind, heißt das noch nicht, dass sie auch binärkonform ist. Soweit ich mich darin eingelesen habe, sind Windows-Exen absolut, was die Speicheradressen angeht (führt, wenn der virtuelle Adressraum, den die Exe beansprucht, noch nicht voll ist - aus diesem Grund besitzen die System-DLLs auch feste Basisadressen, die nicht so häufig benutzt werden - zu einem immensen Geschwindigkeitsvorteil, kann aber auch zu einem massiven Geschwindigkeitsverlust führen, wenn das OS die Adressen umwandeln muss, weil die Adressen bereits belegt sind), während Unix-Exen relative Speicheradressen verwenden.
hustbaer schrieb:
ad 3) - Die grossen VB Programme
Der Grund warum solche Programme so gross werden, ist die dazugelinkte Runtime. Auch ein Visual C++ Programm kann schnell mal ein paar zig oder hundert Kilobyte haben, wenn man die Runtime statisch dazulinkt, und ein paar Runtime-Funktionen verwendet die viele andere mit reinziehen.Verstehe.
hustbaer schrieb:
ad 4) - Das .exe Format
Wenn du wissen willst wie eine Windows .exe aufgebaut ist, dann guck bei Wikipedia nach, Stichwort PE Format (Portable Executable). Linux verwendet soweit ich weiss das ELF Format.Und eine ".exe" "besitzt" ganz sicher keine Register, und wird auch nicht "in Register geladen". Wenn du wissen willst was ein Register ist, empfehle ich ebenso Wikipedia.
Register? Ach du Scheiße, ich meinte Segmente.
Also .text, .data, .rdata und so. Soweit ich weiß, stammen die noch aus der Zeit der 20-Bit-CPUs, als man 1 MB an Speicher reservieren konnte und noch so bescheuerte Konzepte wie FAR- und NEAR-Zeiger.
Ein Register ist ein (eher flüchtiger) Speicher auf der CPU, in dem Werte sehr schnell gesichert und verarbeitet werden können, das weiß ich schon. Ein Segment hingegen enthält Teile der Exe - Code in .text, Daten in .data usw.
hustbaer schrieb:
HIEW ist kein Texteditor.
HIEW unterstützt drei Ansichten einer Datei - Textformat (verwendet dabei wohl die .OEM-Codierung des OS), Hex-Editor-Format und Disassemblierformat. Deshalb kann man es schon als einen Texteditor sehen - mit einigen texteditoruntypischen Features.
-
Nach der Logik könnte man auch Visual-Studio als Zeichenprogramm bezeichnen, weil man damit in Bitmaps rummalen kann. Wäre aber genauso sinnfrei.
"Ich hab da so ein Zeichenprogramm, mit dem kann man auch programmieren"
-
Der aus dem Westen ... schrieb:
Ist das ein Test?
Nein, das sollte nur helfen, den Unterschied einzuordnen. Für Low Level Geschichten ist das Windowsprogramm debug oder ein 32bit Clone davon sehr gut:
C:\Users\nachtfeuer>debug -a 19F4:0100 add cx,dx 19F4:0102 -r AX=0000 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000 DS=19F4 ES=19F4 SS=19F4 CS=19F4 IP=0100 NV UP EI PL NZ NA PO NC 19F4:0100 01D1 ADD CX,DX -u 19F4:0100 01D1 ADD CX,DX 19F4:0102 0000 ADD [BX+SI],AL 19F4:0104 0000 ADD [BX+SI],AL 19F4:0106 0000 ADD [BX+SI],AL 19F4:0108 0000 ADD [BX+SI],AL 19F4:010A 0000 ADD [BX+SI],AL 19F4:010C 0000 ADD [BX+SI],AL 19F4:010E 0000 ADD [BX+SI],AL 19F4:0110 0000 ADD [BX+SI],AL 19F4:0112 0000 ADD [BX+SI],AL 19F4:0114 0000 ADD [BX+SI],AL 19F4:0116 0000 ADD [BX+SI],AL 19F4:0118 0000 ADD [BX+SI],AL 19F4:011A 0000 ADD [BX+SI],AL 19F4:011C 3400 XOR AL,00 19F4:011E E319 JCXZ 0139 -
-
Was spricht denn gegen den Ollydbg?^^
-
nachtfeuer schrieb:
Nein, das sollte nur helfen, den Unterschied einzuordnen.
Maschinencode (so wie ihn mir der Hex-Editor HIEW ausgibt):
8B FF 55 8B EC E8 79 1F FF FF
Mnemonics:
mov edi,edi push ebp mov epb,esp
Wobei mir nicht klar ist, warum man edi nach edi kopieren muss. Sollte mich aber nicht wundern, schließlich handelt es sich hier um den Code der API-Funktion
RegOpenKey
, und wir wissen ja, was Microsoft so alles anstellt ...
-
Der aus dem Westen ... schrieb:
Wobei mir nicht klar ist, warum man edi nach edi kopieren muss. Sollte mich aber nicht wundern, schließlich handelt es sich hier um den Code der API-Funktion
RegOpenKey
, und wir wissen ja, was Microsoft so alles anstellt ...Das hat schon seinen Sinn:
http://msdn.microsoft.com/en-us/library/ms173507.aspx
http://technet.microsoft.com/en-us/library/cc787843(v=WS.10).aspx
-
das ist aber bei bei dieser Adresse hier etwas besser erklärt:
http://blogs.msdn.com/b/ishai/archive/2004/06/24/165143.aspx
-
Wenn ich das also recht verstanden habe, werden diese zwei Byte dafür verwendet, einen Platzhalter für einen kurzen Jumpbefehl zu bilden (nur zwei Bytes), der hinterher auf einen größerem Jumpbefehl verweist. Dies hat den Vorteil, dass man so noch ein Abbild der Funktion, die man patchen will, irgendwo im Speicher hat, sodass es nicht notwendig ist, Anwendungen, die diese Funktionen nutzen, anzuhalten - wenn mein Programm also bspw.
RegOpenKey
verwendet, gerade aber ein Update der Reg-Dll läuft, werden alle Funktionaufrufe zu einer "virtuellen" Funktion umgeleitet, während die "reelle" Funktion umgeschrieben wird.Korrekt?