Ich versuche den Sinn dieses ARM codeschnippsel zu verstehen, mit der Bitte um Kommentierung. Der Code stammt aus einer Steuergeräte-Firmware.
Im Einsprung ist R4 mit einer Speicheradresse im RAM geladen. Hier können 2-Byte Werte von 0xF100 an aufwärts stehen.
R5 wird mit 0 geladen
R0 wird mit dem Byte aus Speicheradresse R4+1 geladen
R1 wird mit dem Byte aus Speicheradresse R4+2 geladen
R0 wird um 8 Bit nach links geschoben (die rechts eintretenden Bits sind 0)
R0 wird mit R0 + R1 geladen
R0 wird um 16 Stellen nach links geschoben
R0 wird um 16 Stellen nach rechts geschoben
R3 wird mit 0xF101 geladen
R0 wird mit R3 verglichen (Flags werden gesetzt)
Bei Gleichheit von R0 und R3 wird nach loc_36a2 verzweigt, ansonsten geht es nach diesem Befehl weiter.
Aus meiner Sicht sind die Befehle 2-5 dazu da einen 16 Bit Wert mit dem offset R4+1 in das Register R0 zu laden.
Die Befehle 6 und 7 sind quasi eine Maskierung der oberen 16 Bit auf 0 (hier wäre auch ein AND R0 mit 0x0000FFFF genauso gut gewesen, evtl. ist Shift schneller?)
Die Befehle 6 und 7 sind quasi eine Maskierung der oberen 16 Bit auf 0
Richtig. Das ist allerdings unnötig, weil diese Bits durch das LDRB bereits auf 0 gesetzt wurden. Das passiert bei schlechter/abgeschalteter Compiler-Optimierung, besonders wenn man zwischen verschiedenen Integertypen hin und her wandelt. IIRC machen alte GCC-Versionen sowas.
r0 und r1 einzeln zu laden (statt direkt 16bit mit LDRH zu laden) wird wahrscheinlich gemacht weil die Adresse nicht aligned ist (d.h. R4 ist gerade, dadurch ist R4+1 ungerade).
(hier wäre auch ein AND R0 mit 0x0000FFFF genauso gut gewesen, evtl. ist
Shift schneller?)
Den Immediate-Wert 0x0000FFFF zu laden ist auch aufwendig. Ist es ein ARMv6 oder v7? Weiß die Details gerade nicht auswendig, aber ich meine insbesondere bei ARMv6 dürfte das 2x shiften effizienter sein.
Ein weiteres Artefakt was vermutlich seinen Ursprung in fehlender Compiler-Optimierung hat?
Ich finde öfter solche Konstrukte im Code die auf den ersten Blick wenig Sinn machen:
1
LDR R1, =byte_0x400017F0
2
MOVS R0, #8
3
STRB R0, [R1,#(byte_400017F3 - 0x400017F0)]
4
MOVS R0, #0x30
5
STRB R0, [R1,#(byte_400017F4 - 0x400017F0)]
6
MOVS R0, #0x61
7
STRB R0, [R1,#(byte_400017F5 - 0x400017F0)]
8
MOVS R0, #0x55
9
STRB R0, [R1,#(byte_400017F6 - 0x400017F0)]
10
MOVS R0, #0xAA
11
STRB R0, [R1,#(byte_400017F7 - 0x400017F0)]
Da werden 8-Bit Werte im Speicher (RAM) gesetzt, vermutlich ein Array. In R1 wird die Basis-Adresse geladen und dann nachfolgend in den STRB die Index-Methode genutzt. Der Offset wird dabei jedesmal berechnet aus der Differenz zweier 32-Bit Speicheradressen. Ich hätte da jetzt eher einfach einen Index gesehen, z.B.
1
LDR R1, =byte_0x400017F0
2
MOVS R0, #8
3
STRB R0, [R1,#3]
4
MOVS R0, #0x30
5
STRB R0, [R1,#4]
6
MOVS R0, #0x61
7
STRB R0, [R1,#5]
8
MOVS R0, #0x55
9
STRB R0, [R1,#6]
10
MOVS R0, #0xAA
11
STRB R0, [R1,#7]
Denke ich da einfach wieder nicht wie ein Compiler, oder gibt es auch hierfür eine gute Erklärung?
signed int __fastcall uds_readDataByIdentifier(unsigned __int8 *a1)
Oh, UDS und der RDBI, das mache ich immer noch beruflich. Ich schätze,
das war damals CAN-basiert. Heutzutage mit DoIP.
Ja genau, auch wenn das hier prinzipiell nichts zur Sache tut, aber ich versuche gerade eine Automotive Firmware in Teilen zu verstehen (Reverse-Engineering) und gleichzeitig ARM-Code lesen zu lernen. Es ist sicher nicht möglich eine komplette Firmware zu rückwärts zu entwickeln, aber bestimmte Komponenten und daraus wird man dann wieder schlauer was die Funktion angeht.
Ich hätte da jetzt eher
einfach einen Index gesehen, z.B.
Genau das steht da auch in den Instruktionen. Das "byte_400017F3 - 0x400017F0" hat der Disassembler da "hilfreich" eingefügt damit du siehst dass es an die Stelle byte_400017F3 kommt.
Ja, die Firmware stammt aus 2005 rum, vielleicht sogar noch älter.
Also ist es vermutlich ein ARM7TDMI, der war ja sehr verbreitet. Dessen Thumb Instruction Set kann maximal 11bit Immediates in eine Instruktion kodieren, und auch das nur für Sprünge. Für arithmetische Operationen und Speicherzugriffe sind nur 8bit-Immediates möglich:
Somit können eine Bitmaske wie 0x0000FFFF oder gar 2 32bit-Werte wie "byte_400017F7 - 0x400017F0" bei Weitem nicht in einer Instruktion kodiert werden.
Laut Datenblatt vom Prozessor wird hier ein 16-Bit Wert hochgezählt mit Überlauf. D.H. diese Routine gibt einen "Zufallswert" im Bereich 0000-FFFF zurück, im Takt des Bit-Clocks für diesen Port (125 kbit/s).
Interessant ist die Verwendung davon:
1
000031B4 createRandom
2
000031B4 PUSH {R4-R6,LR}
3
000031B6 MOVS R4, #0
4
000031B8 LDR R5, =seed ; Hier hin werden die berechneten Zufallsdaten geschrieben
5
000031BA loc_31BA
6
000031BA BL mscan_timerValue ; Hier nach enthält R0 den 16-Bit Zufallswert
7
000031BE MOVS R1, #0
8
000031C0 loc_31C0
9
000031C0 LSLS R3, R0, #1 ; Multiplikation mit 2, also R3=R0*2
Soweit so gut (stimmt hoffentlich alles?)
Kommen wir zum Endgegner ;-)
1
00003214 LDRB R0, [R5,#2]
2
00003216 MOVS R1, R5
3
00003218 CMP R0, #0
4
0000321A BNE loc_3230
5
0000321C CMP R4, #5
6
0000321E BCC loc_322C
7
8
00003220 MOVS R0, #3
9
00003222 STRB R0, [R1]
10
00003224 MOVS R0, #0x1D
11
00003226 STRB R0, [R1,#1]
12
00003228 MOVS R0, #0x65
13
0000322A STRB R0, [R1,#2]
14
15
0000322C loc_322C
16
0000322C CMP R4, #5
17
0000322E BCC loc_31BA
18
19
00003230 loc_3230
20
00003230 POP {R4-R6}
21
00003232 POP {R3}
22
00003234 BX R3
1
if(seed[2]==0)return;
2
if(R4>5){
3
seed[0]=0x03;
4
seed[1]=0x1D;
5
seed[2]=0x65;
6
}
7
}while(R4<=5);
Nochmal zusammengefasst und etwas optimiert auf Lesbarkeit:
1
uint8n=0;
2
uint8seed[3];
3
uint16rnd;
4
do
5
{
6
rnd=timerValue();
7
for(uint8i=0;i<=5;i++)
8
{
9
rnd=(rnd+(rnd*2)+29)&0xFFFF;
10
}
11
seed[1]=(rnd&0xFF);
12
seed[2]=(rnd>>8);
13
14
rnd=timerValue();
15
for(uint8i=0;i<=5;i++)
16
{
17
rnd=((rnd*29)+3)&0xFFFF;
18
}
19
seed[0]=(rnd&0xFF);
20
seed[1]=seed[1]^(rnd>>8);
21
22
if(seed[0]==0||seed[1]==0||seed[2]==0)return;
23
24
n=n+1;
25
if(n>5)
26
{
27
seed[0]=0x03;
28
seed[1]=0x1D;
29
seed[2]=0x65;
30
}
31
}while(n<=5);
Wie man erkennt legt die Routine großen Wert darauf das im Ergebnis keine 0x00 Bytes vorkommen.
Eines macht mich stutzig... die "if (n>5)" würde ja praktisch bei der letzten Iteration aufgerufen und alle so mühsam zuvor berechneten Werte mit diesen statischen überschreiben?!?
Ich glaube ich habe diesen code falsch interpretiert
1
000031BE MOVS R1, #0
2
000031C0 loc_31C0
3
...
4
000031CA ADDS R1, #1
5
000031D0 CMP R1, #5
6
000031D2 BCC loc_31C0
Das Carry-Flag wird bereits gesetzt wenn R1 den Wert 5 hat. Damit hab ich nicht gerechnet, denn Carry ist für mich ein Übertrag und CMP macht doch nichts anderes als SUBS R1, 5# wo ja 0 raus kommt. Das finde ich reichlich merkwürdig, ja sogar unlogisch irgendwie...
Bei manchen Prozessoren entspricht das C-Flag einer Subtraktion nicht der intuitiven Vorstellung, sondern der Addition des Einerkomplements. Das ist dann genau umgekehrt.