.section .data
)Diese Sektion definiert globale Symbole und reserviert Speicherplatz für verschiedene Datenstrukturen, die von den Funktionen im Textabschnitt verwendet werden.
.global num_2_dec
.global num2hexascii
.global float2ascii
.section .data
sign:
.byte 0x0
.balign 4
num2dec_buffer:
.space 16, 0x0
Hex_Lookup:
.asciz "0123456789ABCDEF"
.balign 4
kformat_buffer:
.space 120, 0x0
Hexres:
.ascii "0", "x"
kformat_buffer_reverse:
.space 120, 0x0
.balign 4
Die globalen Symbole num_2_dec
, num2hexascii
und float2ascii
werden für den Zugriff aus anderen Modulen verfügbar gemacht. Im Datenbereich werden Speicherbereiche für verschiedene Zwecke reserviert: sign
dient vermutlich zur Speicherung eines Vorzeichens, während der Puffer num2dec_buffer
für die Umwandlung von Zahlen in Dezimaldarstellungen genutzt wird. Die Zeichenkette Hex_Lookup
unterstützt die Konvertierung von Zahlen in ihre hexadezimale Form. Zusätzliche Puffer wie kformat_buffer
und kformat_buffer_reverse
bieten Speicherplatz für allgemeine Formatierungen und möglicherweise umgekehrte Zeichenfolgen. Hexres
speichert das Präfix "0x"
für hexadezimale Ausgaben.
.section .text
)Diese Sektion enthält die Implementierung der Funktionen zur Umwandlung von Zahlen in ASCII-Darstellungen sowie Hilfsfunktionen zur Pufferbereinigung.
float2ascii
float2ascii:
push {lr}
push {r11}
mov r11, sp
vpush {d0-d15}
vmov s0, r1
mov r2, #0
float_asc:
vcmpe.f32 s0, #0
vmrs APSR_nzcv, FPSCR
movmi r2, #1
vabs.f32 s0, s0
vcvt.s32.f32 s1, s0
vmov r0, s1
vcvt.f32.s32 s1, s1
vsub.f32 s0, s0, s1
mov r1, r0
mov r3, #0
bl num_2_dec
fraction:
add r1, r1, #1
mov r2, #0x2e
strb r2, [r0, r1]
add r1, r1, #1
mov r2, #10
vmov s2, r2
vcvt.f32.s32 s2, s2
vmov.f32 s3, #1
vdiv.f32 s3, s3, s2
vmov s15, s2
vmul.f32 s14, s15, s2
vmul.f32 s13, s14, s2
vmul.f32 s12, s13, s2
vmul.f32 q3, q3, d0[0]
vmul.f32 q2, q3, d1[1]
vcvt.s32.f32 q2, q2
vcvt.f32.s32 q2, q2
vmul.f32 q2, q2, d1[0]
vsub.f32 q1, q3, q2
vcvt.s32.f32 q2, q1
float_fr_save:
mov r2, #0x30
vdup.s32 q3, r2
vadd.s32 q1, q2, q3
vmov r2, s7
strb r2, [r0, r1]
add r1, r1, #1
vmov r2, s6
strb r2, [r0, r1]
add r1, r1, #1
vmov r2, s5
strb r2, [r0, r1]
add r1, r1, #1
vmov r2, s4
strb r2, [r0, r1]
endfl2asc:
vpop {d0-d15}
mov sp, r11
pop {r11}
ldr r0, =kformat_buffer
pop {lr}
bx lr
1. Initialisierung und Vorbereitung
push {lr}
push {r11}
mov r11, sp
vpush {d0-d15}
vmov s0, r1
mov r2, #0
Ja, hier ist die Erklärung in kürzerer und wesentlicherer Form:
1. Initialisierung und Vorbereitung
push {lr}
push {r11}
mov r11, sp
vpush {d0-d15}
vmov s0, r1
mov r2, #0
Die Funktion sichert die Rücksprungadresse und den Framepointer auf dem Stack, speichert die Floating-Point-Register und lädt den Eingabewert (r1
) in das Register s0
. Das Register r2
wird auf 0
gesetzt, um das Vorzeichen des Vorkommateils der Zahl für den späteren Aufruf von num_2_dec
als positiv zu initialisieren.
2. Bestimmen des Vorzeichens und Absolutwertbildung
float_asc:
vcmpe.f32 s0, #0
vmrs APSR_nzcv, FPSCR
movmi r2, #1
vabs.f32 s0, s0
Die Fließkommazahl in s0
wird mit Null verglichen, um das Vorzeichen zu ermitteln. Wenn die Zahl negativ ist, wird die Negativ-Flag gesetzt, und r2
wird auf 1
gesetzt. Anschließend wird der Absolutwert der Zahl berechnet und wieder in s0
gespeichert, um die weitere Verarbeitung zu vereinfachen.
3. Trennung in Ganzzahl- und Bruchteil
vcvt.s32.f32 s1, s0
vmov r0, s1
vcvt.f32.s32 s1, s1
vsub.f32 s0, s0, s1
Der Absolutwert in s0
wird in eine Ganzzahl konvertiert und in s1
gespeichert. Dieser ganzzahlige Wert wird in r0
übertragen. Durch Rückkonvertierung von s1
in eine Fließkommazahl und anschließende Subtraktion von s0
wird der Bruchteil ermittelt und in s0
belassen.
4. Konvertierung des Ganzzahlteils in ASCII
mov r1, r0
mov r3, #0
bl num_2_dec
Der ganzzahlige Wert wird in r1
kopiert, und r3
wird auf 0
gesetzt. Die Funktion num_2_dec
wird aufgerufen, um den Ganzzahlteil in eine ASCII-Zeichenkette zu konvertieren. Das Vorzeichenflag in r2
wird dabei berücksichtigt, sodass num_2_dec
ein Minuszeichen voranstellen kann, wenn die Zahl negativ ist.
5. Einfügen des Dezimalpunkts
fraction:
add r1, r1, #1
mov r2, #0x2e
strb r2, [r0, r1]
add r1, r1, #1
Nach der Konvertierung des Ganzzahlteils wird der Index r1
um 1 erhöht, und der ASCII-Wert für den Dezimalpunkt (.
) wird in den Puffer an der aktuellen Position gespeichert. Der Index wird erneut erhöht, um Platz für die Nachkommastellen zu schaffen.
6. Vorbereitung zur Berechnung der Nachkommastellen
mov r2, #10
vmov s2, r2
vcvt.f32.s32 s2, s2
vmov.f32 s3, #1
vdiv.f32 s3, s3, s2
Der Wert 10
wird in s2
als Fließkommazahl gespeichert. s3
wird auf 1.0
gesetzt und durch s2
geteilt, um 0.1
zu erhalten. Dieser Wert wird als Skalierungsfaktor für die Nachkommastellen verwendet.
7. Berechnung der Potenzen von 10
vmov s15, s2
vmul.f32 s14, s15, s2
vmul.f32 s13, s14, s2
vmul.f32 s12, s13, s2
Es werden die Potenzen von 10 berechnet und in den Registern s12
bis s15
gespeichert. Diese Werte (10, 100, 1000, 10000) werden genutzt, um die Nachkommastellen entsprechend ihrer Stellenwerte zu skalieren.
8. Skalierung und Extraktion der Nachkommastellen
vmul.f32 q3, q3, d0[0]
vmul.f32 q2, q3, d1[1]
vcvt.s32.f32 q2, q2
vcvt.f32.s32 q2, q2
vmul.f32 q2, q2, d1[0]
vsub.f32 q1, q3, q2
vcvt.s32.f32 q2, q1
Der Bruchteil in s0
wird mit den berechneten Potenzen multipliziert, um die Nachkommastellen zu isolieren. Die Ergebnisse werden zwischen Fließkomma- und Ganzzahlformaten konvertiert, um die Ziffern der Nachkommastellen zu erhalten.
9. Konvertierung der Nachkommastellen in ASCII
float_fr_save:
mov r2, #0x30
vdup.s32 q3, r2
vadd.s32 q1, q2, q3
Der ASCII-Wert für ‘0’ (0x30
) wird in alle Elemente des Vektors q3
dupliziert. Durch Addition der extrahierten Nachkommastellen in q2
zu q3
entstehen die ASCII-Codes der entsprechenden Ziffern.
10. Speichern der Nachkommastellen im Puffer
vmov r2, s7
strb r2, [r0, r1]
add r1, r1, #1
vmov r2, s6
strb r2, [r0, r1]
add r1, r1, #1
vmov r2, s5
strb r2, [r0, r1]
add r1, r1, #1
vmov r2, s4
strb r2, [r0, r1]
Die ASCII-Zeichen der Nachkommastellen werden nacheinander in den Puffer geschrieben. Nach jedem Schreibvorgang wird der Index r1
erhöht, um die nächste Position zu adressieren.
11. Abschluss und Rückgabe
endfl2asc:
vpop {d0-d15}
mov sp, r11
pop {r11}
ldr r0, =kformat_buffer
pop {lr}
bx lr
Am Ende werden die Register vom Stack geladen, sp
auf r11
zurückgesetzt, r11
vom Stack geholt und die Adresse von kformat_buffer
in r0
geladen. Schließlich wird lr
wiederhergestellt und zur Rückkehr gesprungen.
num_2_dec
num_2_dec:
push {lr}
push {r11}
mov r11, sp
and r2, r2, #0x3f
push {r1, r2}
bl clear_buff
buff_cleared_dec:
pop {r1, r2}
push {r4,r5, r6, r7}
cmp r2, #1
mov r6, #0
bne dec_signed_processed
mov r2, #0x2d
ldr r3, =sign
strb r2, [r3]
dec_signed_processed:
ldr r4, =kformat_buffer
mov r0, #0
mov r2, #10
mov r3, #0
cmp r1, #0
bne num_2_dec_loop
add r0, r1, #0x30
push {r0}
add r6, #1
b num_2_dec_conv_end
num_2_dec_loop:
mov r3, r1
cmp r3, #0
beq num_2_dec_conv_end
udiv r1, r1, r2
umull r0, r5, r1, r2
sub r0, r3, r0
add r0, #0x30
push {r0}
add r6, #1
b num_2_dec_loop
num_2_dec_conv_end:
cmp r6, #0
beq print_is_d
mov r1, r6
mov r5, r3
print_is_d:
mov r7, #0
ldr r3, =sign
ldrb r2, [r3]
cmp r2, #0x2d
bne print_is_d_loop
strb r2, [r4], #1
print_is_d_loop:
pop {r0}
strb r0, [r4, r7]
cmp r7, r6
add r7, #1
bls print_is_d_loop
num_2_dec_end:
add r1, r5, r1
ldr r3, =sign
ldrb r2, [r3]
cmp r2, #0x2d
subne r1, #1
mov r2, #0
strb r2, [r3]
pop {r4, r5, r6, r7}
mov sp, r11
pop {r11}
ldr r0, =kformat_buffer
pop {lr}
bx lr
push {lr}
push {r11}
mov r11, sp
and r2, r2, #0x3f
push {r1, r2}
bl clear_buff
Im Prolog werden die Rücksprungadresse (lr
) und das Register r11
auf den Stack gesichert, danach wird der Stackzeiger in r11
gespeichert. r2
wird so maskiert, dass nur die unteren 6 Bits erhalten bleiben und dient als Übergabeparameter dazu, der Dezimalzahl gegebenenfalls später ein ASCII-Minuszeichen voranzustellen. Anschließend werden r1
und r2
auf den Stack gelegt, bevor die Funktion clear_buff
zum Leeren des Puffers aufgerufen wird.
clear_buff
:
buff_cleared_dec:
pop {r1, r2}
push {r4,r5, r6, r7}
cmp r2, #1
mov r6, #0
bne dec_signed_processed
mov r2, #0x2d
ldr r3, =sign
strb r2, [r3]
Die Funktion holt zunächst die Register r1
und r2
vom Stack zurück und speichert dann die Register r4
, r5
, r6
und r7
erneut auf dem Stack. Anschließend wird r2
mit 1
verglichen. Ist r2
ungleich 1
, wird zu dec_signed_processed
gesprungen. Ist r2
jedoch gleich 1
, wird ein Minuszeichen (0x2d
) in den sign
-Puffer geschrieben.
dec_signed_processed:
ldr r4, =kformat_buffer
mov r0, #0
mov r2, #10
mov r3, #0
cmp r1, #0
bne num_2_dec_loop
add r0, r1, #0x30
push {r0}
add r6, #1
b num_2_dec_conv_end
In diesem Abschnitt wird die Adresse des kformat_buffer
in r4
geladen, während r0
auf 0
und r2
auf 10
für die Division gesetzt werden. Anschließend wird r1
mit 0
verglichen. Ist r1
nicht gleich 0
, springt der Code zur Schleife num_2_dec_loop
. Wenn r1
jedoch gleich 0
ist, wird 0x30
(ASCII-Wert für '0'
) zu r1
addiert und auf dem Stack gespeichert.
num_2_dec_loop:
mov r3, r1
cmp r3, #0
beq num_2_dec_conv_end
udiv r1, r1, r2
umull r0, r5, r1, r2
sub r0, r3, r0
add r0, #0x30
push {r0}
add r6, #1
b num_2_dec_loop
In der Schleife wird der aktuelle Wert von r1
nach r3
kopiert und anschließend mit 0
verglichen. Ist r3
gleich 0
, endet die Schleife. Andernfalls wird r1
durch 10
geteilt, und das Ergebnis wird wieder in r1
gespeichert. Danach wird r1
mit 10
multipliziert, um das Produkt in r0
zu speichern, und dieses wird von r3
subtrahiert, um den Rest (die aktuelle Ziffer) zu ermitteln. Der ASCII-Wert der Ziffer wird durch Hinzufügen von 0x30
erzeugt und auf dem Stack gespeichert. Schließlich wird der Zähler r6
um 1
erhöht, bevor die Schleife wiederholt wird.
num_2_dec_conv_end:
cmp r6, #0
beq print_is_d
mov r1, r6
mov r5, r3
print_is_d:
mov r7, #0
ldr r3, =sign
ldrb r2, [r3]
cmp r2, #0x2d
bne print_is_d_loop
strb r2, [r4], #1
print_is_d_loop:
pop {r0}
strb r0, [r4, r7]
cmp r7, r6
add r7, #1
bls print_is_d_loop
Die Konvertierung beginnt mit dem Vergleich von r6
(Anzahl der Ziffern) mit 0
. Ist r6
gleich 0
, wird direkt zu print_is_d
gesprungen. Andernfalls wird r1
auf r6
gesetzt und r5
auf r3
. Anschließend wird das Vorzeichen aus dem sign
-Puffer geladen, und falls es ein Minuszeichen (0x2d
) ist, wird es in den Puffer geschrieben. In der Schleife print_is_d_loop
werden die Ziffern vom Stack zurückgeholt und nacheinander in den kformat_buffer
gespeichert. Der Zähler r7
wird inkrementiert, bis alle Ziffern gespeichert sind.
num_2_dec_end:
add r1, r5, r1
ldr r3, =sign
ldrb r2, [r3]
cmp r2, #0x2d
subne r1, #1
mov r2, #0
strb r2, [r3]
pop {r4, r5, r6, r7}
mov sp, r11
pop {r11}
ldr r0, =kformat_buffer
pop {lr}
bx lr
Im Epilog wird r5
zu r1
addiert, um die Gesamtlänge des erstellten Strings zu berechnen. Danach wird das Vorzeichen überprüft, und falls es ein Minuszeichen ist, wird die Länge entsprechend angepasst. Das Vorzeichen im sign
-Puffer wird zurückgesetzt. Anschließend werden die gespeicherten Register wiederhergestellt und die Adresse des Puffers kformat_buffer
in r0
geladen. Schließlich wird die Rücksprungadresse wiederhergestellt, und die Funktion kehrt zurück.
num2hexascii
num2hexascii:
push {lr}
push {r11}
mov r11, sp
push {r4, r5,r6}
push {r1, r2}
bl clear_buff
bl clear_buff_rev
buff_cleared_hex:
pop {r1, r2}
mov r5, #0
mov r3, #0
num_2_ascii_Hex_loop:
and r2, r1, #0xf
ldr r4, =Hex_Lookup
ldrb r4, [r4, r2]
push {r4}
add r3, r3, #1
lsr r1, r1, #4
cmp r1, #0
bne num_2_ascii_Hex_loop
mov r5, r3
ldr r4, =kformat_buffer_reverse
bge hex_save
hex_save:
mov r1, r3
mov r3, #0
sub r1, #1
hex_save_loop:
pop {r0}
strb r0, [r4 ,r3]
add r3, #1
cmp r3, r1
ble hex_save_loop
ldr r0, =kformat_buffer_reverse
ldr r2, =kformat_buffer
sub r5, r5, #1
mov r1, r5
hex_reverse:
push {r1}
ldrb r3, [r0], #1
strb r3, [r2, r1]
subs r1, r1, #1
bge hex_reverse
pop {r1}
mov r1, r5
add r1, r1, #2
pop {r4,r5, r6}
mov sp, r11
pop {r11}
ldr r0, =Hexres
pop {lr}
bx lr
Erklärung:
Die Funktion num2hexascii
konvertiert eine gegebene Ganzzahl in ihre hexadezimale ASCII-Darstellung und speichert sie in einem internen Puffer. Der Rückgabewert r0
zeigt auf den Puffer, und r1
gibt die Länge des erzeugten Strings an. Die Funktion ermöglicht auch die Angabe einer Feldbreite (fieldwidth
) für die Ausgabe.
Schritt-für-Schritt-Erklärung:
push {lr}
push {r11}
mov r11, sp
push {r4, r5,r6}
push {r1, r2}
bl clear_buff
bl clear_buff_rev
Im Prolog werden zunächst die Rücksprungadresse (lr
) und das Register r11
auf den Stack gesichert, und der aktuelle Stackzeiger wird in r11
übertragen. Anschließend werden die Register r4
, r5
, r6
, r1
und r2
auf dem Stack gespeichert. Danach werden die Funktionen clear_buff
und clear_buff_rev
aufgerufen, um die Puffer zu leeren.
clear_buff
und clear_buff_rev
:
buff_cleared_hex:
pop {r1, r2}
mov r5, #0
mov r3, #0
Nach dem Aufruf von clear_buff
und clear_buff_rev
werden die Register r1
und r2
vom Stack geladen. Anschließend werden r5
und r3
auf 0
gesetzt, wobei r5
als Zähler für die Hex-Ziffern dient und r3
als Index fungiert.
num_2_ascii_Hex_loop:
and r2, r1, #0xf
ldr r4, =Hex_Lookup
ldrb r4, [r4, r2]
push {r4}
add r3, r3, #1
lsr r1, r1, #4
cmp r1, #0
bne num_2_ascii_Hex_loop
mov r5, r3
ldr r4, =kformat_buffer_reverse
bge hex_save
In der Schleife num_2_ascii_Hex_loop
werden die unteren 4 Bits von r1
maskiert, um eine Hexadezimalziffer zu extrahieren. Anschließend wird das entsprechende Zeichen aus der Tabelle Hex_Lookup
geladen und auf dem Stack gespeichert. Der Zähler r3
wird erhöht, und r1
wird um 4 Bits nach rechts verschoben, um die nächste Hex-Ziffer zu verarbeiten. Dieser Vorgang wird wiederholt, bis r1
null ist. Schließlich wird r5
auf die Anzahl der extrahierten Ziffern gesetzt, und die Adresse von kformat_buffer_reverse
wird in r4
geladen.
hex_save:
mov r1, r3
mov r3, #0
sub r1, #1
hex_save_loop:
pop {r0}
strb r0, [r4 ,r3]
add r3, #1
cmp r3, r1
ble hex_save_loop
In dieser Funktion wird die Anzahl der Hex-Ziffern in r1
gesetzt und r3
auf 0
initialisiert. Nach einer Anpassung von r1
beginnt die Schleife hex_save_loop
, in der die Hex-Ziffern vom Stack zurückgeholt und in den Puffer kformat_buffer_reverse
geschrieben werden. Der Zähler r3
wird dabei inkrementiert, bis alle Ziffern gespeichert sind.
ldr r0, =kformat_buffer_reverse
ldr r2, =kformat_buffer
sub r5, r5, #1
mov r1, r5
hex_reverse:
push {r1}
ldrb r3, [r0], #1
strb r3, [r2, r1]
subs r1, r1, #1
bge hex_reverse
pop {r1}
mov r1, r5
add r1, r1, #2
Die Funktion lädt die Adressen von kformat_buffer_reverse
und kformat_buffer
in r0
bzw. r2
. r5
wird um 1
reduziert und r1
auf diesen Wert gesetzt. In der Schleife hex_reverse
wird ein Zeichen aus kformat_buffer_reverse
gelesen und an der entsprechenden Position in kformat_buffer
gespeichert. Nach jedem Durchlauf wird r1
dekrementiert, bis alle Zeichen umgekehrt sind. Am Ende wird r1
vom Stack zurückgeholt und um 2
erhöht.
pop {r4,r5, r6}
mov sp, r11
pop {r11}
ldr r0, =Hexres
pop {lr}
bx lr
Die Register r4
, r5
und r6
werden wiederhergestellt, der ursprüngliche Stackzeiger wird zurückgesetzt, und die Adresse des Hex-Präfixes "0x"
wird in r0
geladen. Schließlich wird die Rücksprungadresse wiederhergestellt, und die Funktion kehrt zurück.
clear_buff
clear_buff:
push {lr}
ldr r0, =kformat_buffer
mov r1, #0x00
mov r2, #120
bl memset
pop {lr}
bx lr
Die Funktion clear_buff
setzt den Inhalt des Puffers kformat_buffer
auf null. Zunächst wird die Rücksprungadresse gesichert, dann die Adresse des Puffers in r0
geladen, r1
auf 0x00
gesetzt und r2
mit der Puffergröße von 120 Bytes initialisiert. Anschließend wird die memset
-Funktion aufgerufen, um alle Bytes auf null zu setzen. Zum Abschluss wird die Rücksprungadresse wiederhergestellt, und die Funktion kehrt zurück.
clear_buff_rev
clear_buff_rev:
push {lr}
ldr r0, =kformat_buffer_reverse
mov r1, #0x00
mov r2, #120
bl memset
pop {lr}
bx lr
Die Funktion clear_buff_rev
setzt den gesamten Inhalt des Puffers kformat_buffer_reverse
auf null. Zunächst wird die Rücksprungadresse gesichert, danach wird die Adresse des Puffers in r0
geladen, r1
auf den Wert 0x00
gesetzt und r2
mit der Puffergröße von 120 Bytes initialisiert. Anschließend wird die memset
-Funktion aufgerufen, um alle 120 Bytes auf null zu setzen. Abschließend wird die Rücksprungadresse wiederhergestellt, und die Funktion kehrt zurück.
zurück | Hauptmenü | weiter |