Aliase: Unterschied zwischen den Versionen

Aus C und Assembler mit Raspberry
Die Seite wurde neu angelegt: „== Aliase == -> Verwendung von Aliase, um den Code besser zu verstehen, Problem beim debuggen, objdump, da eventuell andere Aliases verwendet werden. Die von dir beobachtete Diskrepanz resultiert aus dem Unterschied zwischen der **Assembly-Syntax** und der tatsächlichen **Maschinencodierung** sowie der Art und Weise, wie `objdump` und der Assembler die Befehle interpretieren. Lass uns das Schritt für Schritt erklären: --- ### **1. ARM64-Befehlsalias…“
 
KKeine Bearbeitungszusammenfassung
 
(5 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 1: Zeile 1:
== Aliase ==
== Was sind Aliase? ==
-> Verwendung von Aliase, um den Code besser zu verstehen, Problem beim debuggen, objdump, da eventuell andere Aliases verwendet werden.


Die von dir beobachtete Diskrepanz resultiert aus dem Unterschied zwischen der **Assembly-Syntax** und der tatsächlichen **Maschinencodierung** sowie der Art und Weise, wie `objdump` und der Assembler die Befehle interpretieren. Lass uns das Schritt für Schritt erklären:
Ein "Alias" ist eine alternative Schreibweise für eine Instruktion – sozusagen ein „freundlicher“ Name für komplexere Maschinenbefehle. Aliase machen den Code "lesbarer und intuitiver", verändern aber "nicht" den Maschinencode, der letztlich generiert wird.


---
Beispiel:


### **1. ARM64-Befehlsaliasing**
<syntaxhighlight lang="asm">
In ARM64 gibt es viele **Aliase**, d. h. alternative Schreibweisen für denselben Maschinenbefehl. Diese Aliase werden oft verwendet, um die Lesbarkeit des Codes zu verbessern. Beispiele:
mov x0, x1
</syntaxhighlight>


- **`MOV x0, x1`** ist ein Alias für **`ORR x0, xzr, x1`**. 
Dieser Befehl existiert "nicht als eigener Maschinenbefehl", sondern ist ein "Alias" für:
  - Semantik: Kopiere den Inhalt von `x1` nach `x0`.
  - Implementierung: Ein logisches OR mit `xzr` (dem Null-Register) ergibt einfach den Wert von `x1`.


- **`ADD x0, xzr, x1`** ist ebenfalls ein Alias für **`ORR x0, xzr, x1`**. 
<syntaxhighlight lang="asm">
  - Semantik: Addiere `x1` zu `xzr` (immer `0`), was effektiv einfach `x1` ist.
orr x0, x1, xzr
</syntaxhighlight>


---
Der Befehl `orr` (bitweises ODER) mit dem Wert `xzr` (Zero-Register) ergibt effektiv `mov`.


### **2. Warum wird aus `MOV` oder `ADD` ein `ORR`?**
== Warum macht man das? ==
Die ARM64-Maschinenarchitektur verwendet für mehrere Befehle denselben Opcode. Der tatsächlich verwendete Maschinenbefehl für alle drei (`MOV`, `ADD xzr`, `ORR`) lautet **`ORR`**, da dieser Opcode universell genug ist, um die gewünschten Operationen auszuführen.


Der Assembler interpretiert den Code basierend auf der Absicht des Programmierers:
* Lesbarkeit: `mov x0, x1` ist leichter verständlich als `orr x0, x1, xzr`.
- **`MOV`**: Ein Alias, wird zu einem `ORR` kompiliert.
* Portabilität: Viele Architekturen haben einen `mov`, und Entwickler erwarten diesen.
- **`ADD xzr`**: Ein Alias für denselben `ORR`.
* Compilerfreundlich: Auch Compiler wie `clang` oder `gcc` generieren „Alias-ähnlichen“ Code.
- **`ORR`**: Der explizite Maschinenbefehl.


---
== Was macht `objdump` daraus? ==


### **3. Warum zeigt `objdump` wieder `MOV` oder `ADD` an?**
Beim Disassemblieren mit `objdump` oder `gdb` siehst du "immer den echten Maschinenbefehl", nicht den Alias.
Das Tool `objdump` arbeitet nicht nur auf Maschinencode-Ebene, sondern versucht, den Assembly-Code durch **Rückübersetzung** lesbar zu machen. Dabei:
- Entscheidet es sich für die **verständlichste Darstellung**, basierend auf dem Kontext und den Aliases.
- Wenn es sieht, dass der Maschinenbefehl einem Alias entspricht (z. B. `ORR x0, xzr, x1`), gibt es ihn möglicherweise als `MOV x0, x1` oder `ADD x0, xzr, x1` zurück.


In deinem Fall:
<syntaxhighlight lang="asm">
1. Der Assembler hat den Alias `MOV x0, x1` in den Maschinenbefehl `ORR x0, xzr, x1` kompiliert.
mov x0, x1     // geschrieben im Assembler
2. `objdump` hat diesen Maschinenbefehl rückübersetzt und **den Alias gewählt, der am besten passt**.
</syntaxhighlight>
  - Hier hat es sich wieder für `MOV x0, x1` oder `ADD x0, xzr, x1` entschieden, obwohl der zugrunde liegende Befehl tatsächlich `ORR` ist.


---
Ergibt in `objdump -d`:
<syntaxhighlight lang="asm">
orr x0, x1, xzr
</syntaxhighlight>


### **4. Warum nicht immer `ORR`?**
Das kann verwirrend sein, wenn du Quellcode und disassemblierten Code vergleichst!
Tools wie `objdump` bevorzugen eine lesbare Darstellung:
- **`MOV`** ist für Menschen intuitiver als `ORR xzr`.
- Der Assembler und Disassembler abstrahieren unnötige Details, um den Code einfacher zu machen.


---
== Was wird wirklich generiert? ==


### **Zusammenfassung**
Nehmen wir `mov x0, x1`:
Dein Verständnis ist korrekt: Alle drei Befehle (`MOV`, `ADD xzr`, `ORR`) sind **funktional identisch**. `objdump` wählt jedoch je nach Kontext einen Alias aus, der die Intention des Originalcodes besser widerspiegelt.


Es gibt also keinen Fehler in deiner Beobachtung. Wenn du immer den zugrunde liegenden Maschinenbefehl sehen möchtest, kannst du die Binärdatei mit einem Hex-Editor oder Disassembler auf Opcode-Ebene analysieren. Dort siehst du, dass alle drei als `ORR` codiert sind.
* Quelltext (Alias):
: <syntaxhighlight lang="asm">
  mov x0, x1
</syntaxhighlight>
* Assembler-Expansion:
: <syntaxhighlight lang="asm">
  orr x0, x1, xzr
</syntaxhighlight>
* Maschinencode (Hex):
: <syntaxhighlight lang="asm">
  0xaa0103e0
</syntaxhighlight>
 
Ob du `mov x0, x1` oder `orr x0, x1, xzr` schreibst, ergibt "denselben Maschinencode".
 
== Bekannte Aliase im ARM64-Assembler ==
 
{| class="wikitable"
|-
!  Alias !! Entsprechender Instruktionscode  !! Beschreibung
|-
| `mov x0, x1` || `orr x0, x1, xzr` || Kopiere Register
|-
| `mov x0, #0` || `movz x0, #0` || Setze Register auf 0
|-
| `cmp x0, x1` || `subs xzr, x0, x1` || Vergleich über Subtraktion ohne Ergebnis
|-
| `neg x0, x1` || `sub x0, xzr, x1`                              || Negation                               
|-
| `nop`          || `hint #0`                                    || Kein-Operation (für Alignment etc.)   
|-
| `ret`          || `br x30`                                      || Rücksprung aus Funktion               
|-
| `b label`      || `b label` (kein Alias)                        || Unbedingter Sprung                     
|-
| `bl label`      || `bl label` (kein Alias)                      || Sprung mit Link (Funktionsaufruf)     
|-
| `movk/movz/movn`|| Literalladen in 16-Bit-Chunks                || Register initialisieren mit Immediate 
|}
 
== Warum ist das wichtig? ==
 
# Disassembler zeigen keine Aliase → verwirrend bei Analyse.
# Debugging wird leichter, wenn man den echten Maschinenbefehl kennt.
# Assemblerfehler können entstehen, wenn man glaubt, `mov` sei ein „echter“ Befehl mit eigenen Regeln.
 
== Beispiel: `alias_test.s` ==
Hier ist ein einfaches Beispielprogramm in ARM64-Assembler mit typischen "Alias-Instruktionen", das du kompilieren und mit `objdump` untersuchen kannst. Danach zeige ich dir, wie die `objdump`-Ausgabe dazu aussieht.
 
<syntaxhighlight lang="asm">
    .section .text
    .global _start
 
_start:
    // Alias: mov x0, x1 -> orr x0, x1, xzr
    mov x0, x1
 
    // Alias: mov x2, #0 -> movz x2, #0
    mov x2, #0
 
    // Alias: cmp x0, x1 -> subs xzr, x0, x1
    cmp x0, x1
 
    // Alias: neg x3, x4 -> sub x3, xzr, x4
    neg x3, x4
 
    // Alias: nop -> hint #0
    nop
 
    // Alias: ret -> br x30
    ret
</syntaxhighlight>
 
=== Bauen und disassemblieren ===
 
<syntaxhighlight lang="shell">
as alias_test.s -o alias_test.o
ld alias_test.o -o alias_test
objdump -d alias_test
</syntaxhighlight>
 
=== Beispielausgabe von `objdump -d alias_test` ===
 
<syntaxhighlight lang="asm">
0000000000401000 <_start>:
  401000:  aa0103e0    orr    x0, x1, xzr
  401004:  d2800002    movz    x2, #0x0
  401008:  eb01001f    subs    xzr, x0, x1
  40100c:  cb040060    sub    x3, xzr, x4
  401010:  d503201f    nop
  401014:  d65f03c0    ret
</syntaxhighlight>
 
Du siehst:
 
{| class="wikitable"
|-
!  Quellcode Alias !! Disassembler zeigt  !! Bedeutung
|-
| `mov x0, x1`        || `orr x0, x1, xzr`      || Kopiere x1 nach x0                       
|-
| `mov x2, #0`        || `movz x2, #0x0`        || Setze x2 auf 0                           
|-
| `cmp x0, x1`        || `subs xzr, x0, x1`    || Vergleiche x0 mit x1                     
|-
| `neg x3, x4`        || `sub x3, xzr, x4`      || x3 = -x4                                 
|-
| `nop`                || `hint #0`              || Kein Effekt                               
|-
| `ret`                || `ret`                  || Rücksprung über x30                       
|}
 
 
=== Weitere Analyse ===
 
Wenn du "keine Aliase im Disassembly" möchtest (d. h. nur „echte“ Instruktionen), kannst du `objdump` so verwenden:
 
<syntaxhighlight lang="shell">
objdump -d -M no-aliases alias_test
</syntaxhighlight>
 
Das zeigt dir, wie es "der CPU intern" wirklich vorliegt.

Aktuelle Version vom 11. April 2025, 06:41 Uhr

Was sind Aliase?

Ein "Alias" ist eine alternative Schreibweise für eine Instruktion – sozusagen ein „freundlicher“ Name für komplexere Maschinenbefehle. Aliase machen den Code "lesbarer und intuitiver", verändern aber "nicht" den Maschinencode, der letztlich generiert wird.

Beispiel:

mov x0, x1

Dieser Befehl existiert "nicht als eigener Maschinenbefehl", sondern ist ein "Alias" für:

orr x0, x1, xzr

Der Befehl `orr` (bitweises ODER) mit dem Wert `xzr` (Zero-Register) ergibt effektiv `mov`.

Warum macht man das?

  • Lesbarkeit: `mov x0, x1` ist leichter verständlich als `orr x0, x1, xzr`.
  • Portabilität: Viele Architekturen haben einen `mov`, und Entwickler erwarten diesen.
  • Compilerfreundlich: Auch Compiler wie `clang` oder `gcc` generieren „Alias-ähnlichen“ Code.

Was macht `objdump` daraus?

Beim Disassemblieren mit `objdump` oder `gdb` siehst du "immer den echten Maschinenbefehl", nicht den Alias.

mov x0, x1     // geschrieben im Assembler

Ergibt in `objdump -d`:

orr x0, x1, xzr

Das kann verwirrend sein, wenn du Quellcode und disassemblierten Code vergleichst!

Was wird wirklich generiert?

Nehmen wir `mov x0, x1`:

  • Quelltext (Alias):
  mov x0, x1
  • Assembler-Expansion:
  orr x0, x1, xzr
  • Maschinencode (Hex):
  0xaa0103e0

Ob du `mov x0, x1` oder `orr x0, x1, xzr` schreibst, ergibt "denselben Maschinencode".

Bekannte Aliase im ARM64-Assembler

Alias Entsprechender Instruktionscode Beschreibung
`mov x0, x1` `orr x0, x1, xzr` Kopiere Register
`mov x0, #0` `movz x0, #0` Setze Register auf 0
`cmp x0, x1` `subs xzr, x0, x1` Vergleich über Subtraktion ohne Ergebnis
`neg x0, x1` `sub x0, xzr, x1` Negation
`nop` `hint #0` Kein-Operation (für Alignment etc.)
`ret` `br x30` Rücksprung aus Funktion
`b label` `b label` (kein Alias) Unbedingter Sprung
`bl label` `bl label` (kein Alias) Sprung mit Link (Funktionsaufruf)
`movk/movz/movn` Literalladen in 16-Bit-Chunks Register initialisieren mit Immediate

Warum ist das wichtig?

  1. Disassembler zeigen keine Aliase → verwirrend bei Analyse.
  2. Debugging wird leichter, wenn man den echten Maschinenbefehl kennt.
  3. Assemblerfehler können entstehen, wenn man glaubt, `mov` sei ein „echter“ Befehl mit eigenen Regeln.

Beispiel: `alias_test.s`

Hier ist ein einfaches Beispielprogramm in ARM64-Assembler mit typischen "Alias-Instruktionen", das du kompilieren und mit `objdump` untersuchen kannst. Danach zeige ich dir, wie die `objdump`-Ausgabe dazu aussieht.

    .section .text
    .global _start

_start:
    // Alias: mov x0, x1 -> orr x0, x1, xzr
    mov x0, x1

    // Alias: mov x2, #0 -> movz x2, #0
    mov x2, #0

    // Alias: cmp x0, x1 -> subs xzr, x0, x1
    cmp x0, x1

    // Alias: neg x3, x4 -> sub x3, xzr, x4
    neg x3, x4

    // Alias: nop -> hint #0
    nop

    // Alias: ret -> br x30
    ret

Bauen und disassemblieren

as alias_test.s -o alias_test.o
ld alias_test.o -o alias_test
objdump -d alias_test

Beispielausgabe von `objdump -d alias_test`

0000000000401000 <_start>:
  401000:   aa0103e0    orr     x0, x1, xzr
  401004:   d2800002    movz    x2, #0x0
  401008:   eb01001f    subs    xzr, x0, x1
  40100c:   cb040060    sub     x3, xzr, x4
  401010:   d503201f    nop
  401014:   d65f03c0    ret

Du siehst:

Quellcode Alias Disassembler zeigt Bedeutung
`mov x0, x1` `orr x0, x1, xzr` Kopiere x1 nach x0
`mov x2, #0` `movz x2, #0x0` Setze x2 auf 0
`cmp x0, x1` `subs xzr, x0, x1` Vergleiche x0 mit x1
`neg x3, x4` `sub x3, xzr, x4` x3 = -x4
`nop` `hint #0` Kein Effekt
`ret` `ret` Rücksprung über x30


Weitere Analyse

Wenn du "keine Aliase im Disassembly" möchtest (d. h. nur „echte“ Instruktionen), kannst du `objdump` so verwenden:

objdump -d -M no-aliases alias_test

Das zeigt dir, wie es "der CPU intern" wirklich vorliegt.