Forum: Projekte & Code libvmcu - Virtual MCU Library


von David M. (milo-d)


Lesenswert?

Hallo,

dies soll der neue Update-Log Thread werden, da ich das User Interface 
rausgeworfen habe und es somit nicht mehr viel mit einem Debugger zu tun 
hat.

Übrig geblieben ist nun die Engine für Simulation und Analyse von AVR 
Binaries. Und um die soll es nun hier gehen. Das ganze habe ich libvmcu 
(Library for Virtual Microcontroller) genannt.

Features:
--------------

- statische Analyse (SFRs, ISRs, mögliche Labels, Cycle Analyse, ...)
- Echtzeit Simulation inklusive Peripherie und Backstepping
- Java Binding

Usecases:
--------------

libvmcu kann verwendet werden um

- Regression Tests für AVR Projekte zu erstellen
- eigene Tools (Simulatoren oder kleine Utilities) zu erstellen
- detaillierte Informationen aus dem Binary zu erhalten

Beispiel:
--------------

Dieses Beispiel stellt einen möglichen Regressiontest für ein ATmega328P 
Programm dar.

Das zu testende Programm: Eine angeschlossene LED an PB5. Die LED soll 
im Takt von einer Sekunde getoggled werden. Dazu wird der Timer0 im 
Normal Mode verwendet und nach einer gewissen Anzahl an Overflows wird 
die LED getoggled. Der Timer wurde mit berechneten Werten konfiguriert, 
somit ist zu erwarten, dass dieser auch im richtigen Takt toggled.

Dies soll nun dennoch verifiziert werden, dabei ist eine Abweichung von 
maximal +-0.05 Sekunden erlaubt. Der Test ist also erfolgreich, wenn die 
Zeit zwischen dem LED toggling zwischen 0.95 und 1.05 Sekunden liegt.
1
#define TESTFILE  "../../driver/led/led.hex"
2
3
#define PORTB     0x0025
4
#define PB5       0x05
5
6
#define bit(v, b) ((v & (1 << b)) >> b)
7
8
int main(const int argc, const char **argv) {
9
10
    uint8_t led;
11
    
12
    /* ignoring checks for this example */
13
    vmcu_report_t *report = vmcu_analyze_ihex(TESTFILE);
14
    vmcu_system_t *sys    = vmcu_system_ctor(report);
15
    
16
    do {
17
18
        vmcu_system_step(sys);
19
        led = vmcu_system_read_data(sys, PORTB);
20
21
    } while(bit(led, PB5) == 0x00);
22
23
    const double f    = 16000000U;
24
    const double c    = sys->cycles;
25
    const double time = (c / f);
26
    
27
    assert((0.95 <= time) && (time <= 1.05));
28
    printf("Time between LED toggle: %lf [s]\n", time);
29
    
30
    vmcu_report_dtor(report);
31
    vmcu_system_dtor(sys);
32
33
    return EXIT_SUCCESS;
34
}
1
Test Resultat: Time between LED toggle: 1.000021 [s]

Dies ist natürlich nur ein vereinfachtes Beispiel und soll lediglich das 
Prinzip dahinter verdeutlichen.

Link: https://github.com/Milo-D/libvmcu-Virtual-MCU-Library

: Bearbeitet durch User
von David M. (milo-d)


Angehängte Dateien:

Lesenswert?

Update (v.0.7.3)

-> libvmcu bietet nun xrefs an (cross references). Der Analyzer kann nun 
Referenzen setzten, sodass man diese dann später einsehen kann um 
beispielsweise herauszufinden, welche Instruktion ein potentielles Label 
referenziert.

-> VCD Trace Tool (driver) wurde hinzugefügt. Dieser driver wird wie 
folgt aufgerufen: ./vcdump <file.hex> <cycles> <dataspace address>

Dabei wird die Datei <file.hex> solange simuliert bis <cycles> erreicht 
wird. Währenddessen wird der Wert der <dataspace address> aufgezeichnet 
(es können mehrere Addressen angegeben werden) und als .vcd exportiert. 
Diese kann dann mit bspw. gtkwave interpretiert werden (siehe Screenshot 
im Anhang).

-> ein kleiner Bugfix, welches die 'or' Instruktion betrifft.


xref Beispiel:
----------------
1
int main(void) {
2
3
    /* ignoring checks for this example */
4
    vmcu_report_t *report = vmcu_analyze_ihex("file.hex");
5
6
    for(int32_t i = 0; i < report->nlabels; i++) {
7
8
        vmcu_label_t *lx = &report->labels[i];
9
        printf("0x%04x\tL%d\n\n", lx->addr, lx->id);
10
11
        for(int32_t j = 0; j < lx->nxrefs; j++) {
12
13
            vmcu_xref_t *x = &lx->xrefs[j];
14
15
            printf(" xref from 0x%04x ", x->p->addr);
16
            printf("(%s)\n", x->p->mnem);
17
        }
18
19
        printf("\n");
20
    }
21
22
    vmcu_report_dtor(report);
23
    return EXIT_SUCCESS;
24
}
1
Output:
2
3
0x0000  L0
4
5
 xref from 0x0051 (jmp +0 ; PC <- 0x0)
6
7
0x0034  L1
8
9
 xref from 0x0000 (jmp +52 ; PC <- 0x34)
10
11
0x0040  L2
12
13
 xref from 0x0044 (brne -5 ; (Z = 0): PC <- PC - 0x5 + 1)
14
15
0x0042  L3
16
17
 xref from 0x003f (rjmp +2 ; PC <- PC + 0x2 + 1)
18
19
0x0049  L4
20
21
 xref from 0x004c (brne -4 ; (Z = 0): PC <- PC - 0x4 + 1)
22
23
0x004a  L5
24
25
 xref from 0x0048 (rjmp +1 ; PC <- PC + 0x1 + 1)
26
27
0x0051  L6
28
29
 xref from 0x0002 (jmp +81 ; PC <- 0x51)
30
 xref from 0x0004 (jmp +81 ; PC <- 0x51)
31
 xref from 0x0006 (jmp +81 ; PC <- 0x51)
32
 xref from 0x0008 (jmp +81 ; PC <- 0x51)
33
 xref from 0x000a (jmp +81 ; PC <- 0x51)
34
 xref from 0x000c (jmp +81 ; PC <- 0x51)
35
 xref from 0x000e (jmp +81 ; PC <- 0x51)
36
 xref from 0x0010 (jmp +81 ; PC <- 0x51)
37
 xref from 0x0012 (jmp +81 ; PC <- 0x51)
38
 xref from 0x0014 (jmp +81 ; PC <- 0x51)
39
 xref from 0x0016 (jmp +81 ; PC <- 0x51)
40
 xref from 0x0018 (jmp +81 ; PC <- 0x51)
41
 xref from 0x001a (jmp +81 ; PC <- 0x51)
42
 xref from 0x001c (jmp +81 ; PC <- 0x51)
43
 xref from 0x001e (jmp +81 ; PC <- 0x51)
44
 xref from 0x0020 (jmp +81 ; PC <- 0x51)
45
 xref from 0x0022 (jmp +81 ; PC <- 0x51)
46
 xref from 0x0024 (jmp +81 ; PC <- 0x51)
47
 xref from 0x0026 (jmp +81 ; PC <- 0x51)
48
 xref from 0x0028 (jmp +81 ; PC <- 0x51)
49
 xref from 0x002a (jmp +81 ; PC <- 0x51)
50
 xref from 0x002e (jmp +81 ; PC <- 0x51)
51
 xref from 0x0030 (jmp +81 ; PC <- 0x51)
52
 xref from 0x0032 (jmp +81 ; PC <- 0x51)

von David M. (milo-d)


Lesenswert?

Ich suche zurzeit noch nach Leuten, die gerne was zum Projekt beitragen 
würden.

Bei folgenden Teilen des Projekts bräuchte ich noch Unterstützung:

Bindings
------------------

Ein Python Binding ist zurzeit in Planung. Ein Python Binding würde es 
ermöglichen, sehr schnell kleine Skripte zu schreiben, die mit der AVR 
binary interagieren.

Das Binding müsste dann auch maintained werden, da sich die libvmcu 
library in C auch verändern kann.

Prinzipiell bildet ein Binding ein Subsystem der Repository für welches 
man dann selbst verantwortlich wäre.

Sollte mal das Binding eine längere Zeit nicht gebaut werden können, ist 
dies zwar Schade aber kein großes Problem. Das Binding wird dann einfach 
archiviert bis sich wieder einer bereit erklärt die Arbeit am Binding 
wieder aufzunehmen.

Bindings für andere Sprachen sind zwar nicht geplant, jedoch hätte ich 
nichts dagegen, solange diese auch gepflegt werden. Zurzeit wird das 
Java Binding von pointbazaar maintained.

Bindings generell sind, dadurch dass sich das Library Interface 
verändern kann und man somit das Binding adjustieren muss, ein etwas 
höherer Aufwand.

Driver
--------------

Driver dagegen sind weniger aufwendig. Wenn man nur wenig Zeit hat aber 
dennoch etwas beitragen möchte, sind Driver genau richtig.

Ein Driver ist eine Art Hilfsprogramm (utility) oder Beispielprogramm, 
das gewisse Funktionen erfüllt indem es die libvmcu library benutzt.

Ein Beispiel für einen Driver wäre der "findisr" driver, welcher 
mithilfe von libvmcu die ISRs in einer AVR binary ausfindig macht.

Bei den Drivers gibt es keine genauen Vorgaben. Man kann schreiben wozu 
man Lust hat, solange es Funktionen aus der libvmcu verwendet und 
kombiniert um neue Funktionen zu kreieren. Dabei müssen die Funktionen 
keineswegs kompliziert sein.

Die Driver werden von mir im Nachhinein noch maintained, sodass sie auch 
Veränderungen an der Library überstehen und nicht kaputt gehen.

Tests
------------

Weitere Tests werden ebenfalls benötigt. Generell werden so viele Tests 
benötigt, dass ich alleine da nicht mehr hinterher kommen werde.


Würde mich freuen falls jemand Interesse daran hätte.

von David M. (milo-d)


Lesenswert?

Update (v.0.7.4)

-> Die vmcu_sfr_t Struktur wurde dem Analyzer Report hinzugefügt. Diese 
beinhaltet eine SFR ID und eine Liste an cross references (xrefs). Die 
ID gibt an um welches konkrete SFR es sich handelt und die xrefs sind 
eine Referenz (bzw. Zeiger) auf die Stelle im Disassembly an dem dieses 
SFR angesprochen wird.

-> Es existiert nun ein neues enum (SFREGISTER). Die bereits oben 
erwähnte SFR ID ist von genau diesem Typ. Dies ermöglicht es sehr 
einfach SFRs zu filtern. Ein Beispiel wäre VMCU_EECR oder VMCU_RESERVED.

-> Weitere Verbesserungen am SFR Analyzer.

-> Kleinere Änderungen an der Namenskonvention von vmcu_report_t 
membern. Listen erhalten den Namen der Structure im Singular. Die Listen 
Counter erhalten den Namen der Structure im Singular inklusive des 
Prefix n_

-> Bugfix im occurence driver

-> COM(A/B)x bits überschreiben nun auch die normale Portfunktion

xref (SFR) Beispiel
-----------------------
1
#define FILE "avr_filesystem/avr_filesystem.hex"
2
3
int main(const int argc, const char **argv) {
4
5
    /* ignoring checks for this example */
6
    vmcu_report_t *report = vmcu_analyze_ihex(FILE);
7
8
    for(int32_t i = 0; i < report->n_sfr; i++) {
9
10
        vmcu_sfr_t *sfr = &report->sfr[i];
11
        printf("SFR ID: %d\n", sfr->id);
12
13
        for(int32_t j = 0; j < sfr->n_xref; j++) {
14
15
            vmcu_xref_t *x = &sfr->xref[j];
16
17
            printf("  xref from 0x%04x ", x->p->addr);
18
            printf("(%s)\n", x->p->mnem);
19
        }
20
21
        printf("\n");
22
    }
23
24
    vmcu_report_dtor(report);
25
    return EXIT_SUCCESS;
26
}
1
Output:
2
3
SFR ID: 0
4
  xref from 0x0112 (out 0x01, r16 ; IO[addr] <- R16)
5
  xref from 0x011c (out 0x00, r16 ; IO[addr] <- R16)
6
7
SFR ID: 33
8
  xref from 0x011f (in r16, 0x31 ; R16 <- IO[addr])
9
10
SFR ID: 38
11
  xref from 0x0152 (out 0x3d, r28 ; IO[addr] <- R28)
12
  xref from 0x016f (in r28, 0x3d ; R28 <- IO[addr])
13
  xref from 0x018d (in r28, 0x3d ; R28 <- IO[addr])
14
  xref from 0x01a0 (in r28, 0x3d ; R28 <- IO[addr])
15
  xref from 0x01c5 (in r28, 0x3d ; R28 <- IO[addr])
16
  xref from 0x01df (in r28, 0x3d ; R28 <- IO[addr])
17
  xref from 0x01fc (in r28, 0x3d ; R28 <- IO[addr])
18
  xref from 0x020c (in r28, 0x3d ; R28 <- IO[addr])
19
  xref from 0x021c (in r28, 0x3d ; R28 <- IO[addr])
20
  xref from 0x0223 (out 0x3d, r28 ; IO[addr] <- R28)
21
  xref from 0x0264 (out 0x3d, r28 ; IO[addr] <- R28)
22
  xref from 0x026d (in r28, 0x3d ; R28 <- IO[addr])
23
  xref from 0x0296 (in r28, 0x3d ; R28 <- IO[addr])
24
  xref from 0x029e (out 0x3d, r28 ; IO[addr] <- R28)
25
  xref from 0x02e1 (out 0x3d, r28 ; IO[addr] <- R28)
26
  xref from 0x02e8 (in r28, 0x3d ; R28 <- IO[addr])
27
  xref from 0x0357 (in r28, 0x3d ; R28 <- IO[addr])
28
  xref from 0x03f4 (out 0x3d, r28 ; IO[addr] <- R28)
29
  xref from 0x03fd (in r28, 0x3d ; R28 <- IO[addr])
30
  xref from 0x04c2 (out 0x3d, r28 ; IO[addr] <- R28)
31
  xref from 0x04d0 (in r28, 0x3d ; R28 <- IO[addr])
32
  xref from 0x04d8 (out 0x3d, r28 ; IO[addr] <- R28)
33
  xref from 0x0a35 (out 0x3d, r28 ; IO[addr] <- R28)
34
  xref from 0x0a44 (in r28, 0x3d ; R28 <- IO[addr])
35
  xref from 0x0b65 (in r28, 0x3d ; R28 <- IO[addr])
36
  xref from 0x0b96 (in r28, 0x3d ; R28 <- IO[addr])
37
  xref from 0x0b9d (out 0x3d, r28 ; IO[addr] <- R28)
38
  xref from 0x0c34 (out 0x3d, r28 ; IO[addr] <- R28)
39
  xref from 0x0c3a (in r28, 0x3d ; R28 <- IO[addr])
40
  xref from 0x0c41 (out 0x3d, r28 ; IO[addr] <- R28)
41
  xref from 0x0ca1 (out 0x3d, r28 ; IO[addr] <- R28)
42
  xref from 0x0caf (in r28, 0x3d ; R28 <- IO[addr])
43
  xref from 0x0cb6 (out 0x3d, r28 ; IO[addr] <- R28)
44
  xref from 0x0d24 (out 0x3d, r28 ; IO[addr] <- R28)
45
  xref from 0x0d33 (in r28, 0x3d ; R28 <- IO[addr])
46
  xref from 0x0d8e (in r28, 0x3d ; R28 <- IO[addr])
47
  xref from 0x0d95 (out 0x3d, r28 ; IO[addr] <- R28)
48
  xref from 0x12a4 (in r28, 0x3d ; R28 <- IO[addr])
49
  xref from 0x12ac (out 0x3d, r28 ; IO[addr] <- R28)
50
  xref from 0x12c6 (out 0x3d, r28 ; IO[addr] <- R28)
51
52
SFR ID: 39
53
  xref from 0x0151 (out 0x3e, r29 ; IO[addr] <- R29)
54
  xref from 0x0170 (in r29, 0x3e ; R29 <- IO[addr])
55
  xref from 0x018e (in r29, 0x3e ; R29 <- IO[addr])
56
  xref from 0x01a1 (in r29, 0x3e ; R29 <- IO[addr])
57
  xref from 0x01c6 (in r29, 0x3e ; R29 <- IO[addr])
58
  xref from 0x01e0 (in r29, 0x3e ; R29 <- IO[addr])
59
  xref from 0x01fd (in r29, 0x3e ; R29 <- IO[addr])
60
  xref from 0x020d (in r29, 0x3e ; R29 <- IO[addr])
61
  xref from 0x021d (in r29, 0x3e ; R29 <- IO[addr])
62
  xref from 0x0221 (out 0x3e, r29 ; IO[addr] <- R29)
63
  xref from 0x0262 (out 0x3e, r29 ; IO[addr] <- R29)
64
  xref from 0x026e (in r29, 0x3e ; R29 <- IO[addr])
65
  xref from 0x0297 (in r29, 0x3e ; R29 <- IO[addr])
66
  xref from 0x029c (out 0x3e, r29 ; IO[addr] <- R29)
67
  xref from 0x02df (out 0x3e, r29 ; IO[addr] <- R29)
68
  xref from 0x02e9 (in r29, 0x3e ; R29 <- IO[addr])
69
  xref from 0x0358 (in r29, 0x3e ; R29 <- IO[addr])
70
  xref from 0x03f2 (out 0x3e, r29 ; IO[addr] <- R29)
71
  xref from 0x03fe (in r29, 0x3e ; R29 <- IO[addr])
72
  xref from 0x04c0 (out 0x3e, r29 ; IO[addr] <- R29)
73
  xref from 0x04d1 (in r29, 0x3e ; R29 <- IO[addr])
74
  xref from 0x04d6 (out 0x3e, r29 ; IO[addr] <- R29)
75
  xref from 0x0a33 (out 0x3e, r29 ; IO[addr] <- R29)
76
  xref from 0x0a45 (in r29, 0x3e ; R29 <- IO[addr])
77
  xref from 0x0b66 (in r29, 0x3e ; R29 <- IO[addr])
78
  xref from 0x0b97 (in r29, 0x3e ; R29 <- IO[addr])
79
  xref from 0x0b9b (out 0x3e, r29 ; IO[addr] <- R29)
80
  xref from 0x0c32 (out 0x3e, r29 ; IO[addr] <- R29)
81
  xref from 0x0c3b (in r29, 0x3e ; R29 <- IO[addr])
82
  xref from 0x0c3f (out 0x3e, r29 ; IO[addr] <- R29)
83
  xref from 0x0c9f (out 0x3e, r29 ; IO[addr] <- R29)
84
  xref from 0x0cb0 (in r29, 0x3e ; R29 <- IO[addr])
85
  xref from 0x0cb4 (out 0x3e, r29 ; IO[addr] <- R29)
86
  xref from 0x0d22 (out 0x3e, r29 ; IO[addr] <- R29)
87
  xref from 0x0d34 (in r29, 0x3e ; R29 <- IO[addr])
88
  xref from 0x0d8f (in r29, 0x3e ; R29 <- IO[addr])
89
  xref from 0x0d93 (out 0x3e, r29 ; IO[addr] <- R29)
90
  xref from 0x12a5 (in r29, 0x3e ; R29 <- IO[addr])
91
  xref from 0x12aa (out 0x3e, r29 ; IO[addr] <- R29)
92
  xref from 0x12c4 (out 0x3e, r29 ; IO[addr] <- R29)
93
94
SFR ID: 40
95
  xref from 0x014e (out 0x3f, r1 ; IO[addr] <- R1)
96
  xref from 0x021f (in r0, 0x3f  ; R0 <- IO[addr])
97
  xref from 0x0222 (out 0x3f, r0 ; IO[addr] <- R0)
98
  xref from 0x0260 (in r0, 0x3f  ; R0 <- IO[addr])
99
  xref from 0x0263 (out 0x3f, r0 ; IO[addr] <- R0)
100
  xref from 0x029a (in r0, 0x3f  ; R0 <- IO[addr])
101
  xref from 0x029d (out 0x3f, r0 ; IO[addr] <- R0)
102
  xref from 0x02dd (in r0, 0x3f  ; R0 <- IO[addr])
103
  xref from 0x02e0 (out 0x3f, r0 ; IO[addr] <- R0)
104
  xref from 0x03f0 (in r0, 0x3f  ; R0 <- IO[addr])
105
  xref from 0x03f3 (out 0x3f, r0 ; IO[addr] <- R0)
106
  xref from 0x04be (in r0, 0x3f  ; R0 <- IO[addr])
107
  xref from 0x04c1 (out 0x3f, r0 ; IO[addr] <- R0)
108
  xref from 0x04d4 (in r0, 0x3f  ; R0 <- IO[addr])
109
  xref from 0x04d7 (out 0x3f, r0 ; IO[addr] <- R0)
110
  xref from 0x0a31 (in r0, 0x3f  ; R0 <- IO[addr])
111
  xref from 0x0a34 (out 0x3f, r0 ; IO[addr] <- R0)
112
  xref from 0x0b99 (in r0, 0x3f  ; R0 <- IO[addr])
113
  xref from 0x0b9c (out 0x3f, r0 ; IO[addr] <- R0)
114
  xref from 0x0c30 (in r0, 0x3f  ; R0 <- IO[addr])
115
  xref from 0x0c33 (out 0x3f, r0 ; IO[addr] <- R0)
116
  xref from 0x0c3d (in r0, 0x3f  ; R0 <- IO[addr])
117
  xref from 0x0c40 (out 0x3f, r0 ; IO[addr] <- R0)
118
  xref from 0x0c9d (in r0, 0x3f  ; R0 <- IO[addr])
119
  xref from 0x0ca0 (out 0x3f, r0 ; IO[addr] <- R0)
120
  xref from 0x0cb2 (in r0, 0x3f  ; R0 <- IO[addr])
121
  xref from 0x0cb5 (out 0x3f, r0 ; IO[addr] <- R0)
122
  xref from 0x0d20 (in r0, 0x3f  ; R0 <- IO[addr])
123
  xref from 0x0d23 (out 0x3f, r0 ; IO[addr] <- R0)
124
  xref from 0x0d91 (in r0, 0x3f  ; R0 <- IO[addr])
125
  xref from 0x0d94 (out 0x3f, r0 ; IO[addr] <- R0)
126
  xref from 0x12a8 (in r0, 0x3f  ; R0 <- IO[addr])
127
  xref from 0x12ab (out 0x3f, r0 ; IO[addr] <- R0)
128
  xref from 0x12c2 (in r0, 0x3f  ; R0 <- IO[addr])
129
  xref from 0x12c5 (out 0x3f, r0 ; IO[addr] <- R0)

von David M. (milo-d)


Lesenswert?

Update (v.0.8.0)

-> Devices und Device Model hinzugefügt.

Ein Device Model ist eine Abstraktion über verschiedene AVRs. Es 
beinhaltet Mikrocontroller spezifische Daten, wie beispielsweise 
Speichersektionen und Speicherlayouts.

Jedes implementierte device besitzt einen Device-Loader, welches das 
Device Model mit relevanten Daten bezüglich des AVR Typen füttert. Das 
Model kann dem Analyzer übergeben werden. Dieser analysiert dann das 
Programm im Bezug auf das übergebene Model.

Dieses neue Feature vereinfacht es neue AVR Typen in die statische 
Analyse zu integrieren.

Schritte zum hinzufügen eines neuen AVRs:

    1.) erstelle das neue device arch/device/new_avr.c
    2.) device-loader Funktion load_device_new_avr in 
arch/device/new_avr.c
    3.) befülle das device model mit den Daten zum neuen AVR
    4.) verbinde die device-loader Funktion im Konstruktor des device 
models

Damit wurde nun der AVR new_avr der stattischen Analyse hinzugefügt.

Bei einigen AVRs müsste man sicherlich noch das device model ein wenig 
adjustieren, da dieser noch nicht alle Typen berücksichtigt.

-> vmcu_ prefix fehlte bei einigen Funktionen noch. Dies wurde hiermit 
ergänzt. Damit dürften nun keinen Nameclashes mehr auftauchen (es sei 
denn  natürlich man verwendet absichtlich denselben prefix).

-> weitere tests für den Decoder (com -> sbic)

-> einige Bugfixes

Beispiel Device Models
-------------------------
1
int main(const int argc, const char **argv) {
2
3
    /* ignoring checks for this example */
4
5
    vmcu_model_t  *m328p  = vmcu_model_ctor(VMCU_M328P);       // ATmega328P
6
    vmcu_report_t *report = vmcu_analyze_ihex(argv[1], m328p);
7
8
    for(int32_t i = 0; i < report->progsize; i++) {
9
10
        vmcu_plain_t *p = &report->disassembly[i];
11
12
        if(p->src.type == VMCU_REGISTER && p->src.value == 16) // filter src = r16
13
            printf("0x%04x\t%s\n", p->addr, p->mnem);
14
    }
15
16
    vcmu_report_dtor(report);
17
    vmcu_model_dtor(m328p);
18
19
    return EXIT_SUCCESS;
20
}

von David M. (milo-d)


Lesenswert?

Update (v.0.8.1)

-> Hinzugefügt: Gruppen für Instruktionen. Diese richten sich nach dem 
offiziellen Instruction Set Manual. Bis auf, dass die "cp" (compare) 
Instruktionen in die VMCU_GROUP_MATH_LOGIC Gruppe aufgenommen wurden, da 
diese nun wirklich nichts in der Flow Gruppe zu suchen haben. Wer auch 
immer auf die Idee gekommen ist.

  1.) arithmetic/logic instructions (VMCU_GROUP_MATH_LOGIC)
  2.) data transfer instructions    (VMCU_GROUP_TRANSFER)
  3.) MCU control instructions      (VMCU_GROUP_SYS_CTRL)
  4.) flow control instructions     (VMCU_GROUP_FLOW)
  5.) bit and bit-test instructions (VMCU_GROUP_BIT)

-> vmcu_plain_t nach vmcu_instr_t umbenannt

Beispiel (Filtern von FLOW Instruktionen in file.hex)
-------------------------------------------------------
1
int main(const int argc, const char **argv) {
2
3
    /* ignoring checks for this example */
4
    vmcu_model_t  *m328p  = vmcu_model_ctor(VMCU_M328P);
5
    vmcu_report_t *report = vmcu_report_ctor("file.hex", m328p);
6
7
    for(int32_t i = 0; i < report->progsize; i++) {
8
9
        vmcu_instr_t *instr = &report->disassembly[i];
10
11
        if(instr->group != VMCU_GROUP_FLOW)
12
            continue;
13
14
        printf("0x%04x\t", instr->addr);
15
        printf("%s\n", instr->mnem);
16
    }
17
18
    vmcu_report_dtor(report);
19
    vmcu_model_dtor(m328p);
20
21
    return EXIT_SUCCESS;
22
}

Output
--------
1
0x0000  jmp +52                   ; PC <- 0x34
2
0x0002  jmp +81                   ; PC <- 0x51
3
0x0004  jmp +81                   ; PC <- 0x51
4
0x0006  jmp +81                   ; PC <- 0x51
5
0x0008  jmp +81                   ; PC <- 0x51
6
0x000a  jmp +81                   ; PC <- 0x51
7
0x000c  jmp +81                   ; PC <- 0x51
8
0x000e  jmp +81                   ; PC <- 0x51
9
0x0010  jmp +81                   ; PC <- 0x51
10
0x0012  jmp +81                   ; PC <- 0x51
11
0x0014  jmp +81                   ; PC <- 0x51
12
0x0016  jmp +81                   ; PC <- 0x51
13
0x0018  jmp +81                   ; PC <- 0x51
14
0x001a  jmp +81                   ; PC <- 0x51
15
0x001c  jmp +81                   ; PC <- 0x51
16
0x001e  jmp +81                   ; PC <- 0x51
17
0x0020  jmp +81                   ; PC <- 0x51
18
0x0022  jmp +81                   ; PC <- 0x51
19
0x0024  jmp +81                   ; PC <- 0x51
20
0x0026  jmp +81                   ; PC <- 0x51
21
0x0028  jmp +81                   ; PC <- 0x51
22
0x002a  jmp +81                   ; PC <- 0x51
23
0x002c  jmp +83                   ; PC <- 0x53
24
0x002e  jmp +81                   ; PC <- 0x51
25
0x0030  jmp +81                   ; PC <- 0x51
26
0x0032  jmp +81                   ; PC <- 0x51
27
0x003f  rjmp +2                   ; PC <- PC + 0x2 + 1
28
0x0044  brne -5                   ; (Z = 0): PC <- PC - 0x5 + 1
29
0x0048  rjmp +1                   ; PC <- PC + 0x1 + 1
30
0x004c  brne -4                   ; (Z = 0): PC <- PC - 0x4 + 1
31
0x004d  call +130                 ; PC <- 0x82
32
0x004f  jmp +134                  ; PC <- 0x86
33
0x0051  jmp +0                    ; PC <- 0x0
34
0x0071  cpse r24, r1              ; (R24 = R1): PC <- skip
35
0x0072  rjmp +9                   ; PC <- PC + 0x9 + 1
36
0x007b  reti                      ; PC <- DATA[SP]
37
0x0081  rjmp -14                  ; PC <- PC - 0xe + 1
38
0x0085  rjmp -1                   ; PC <- PC - 0x1 + 1
39
0x0087  rjmp -1                   ; PC <- PC - 0x1 + 1

: Bearbeitet durch User
von David M. (milo-d)


Lesenswert?

Update (v.0.8.2)

-> Interrupt Vektor Analyse wurde hinzugefügt. Es werden nun Vektoren 
erkannt und im Report zur Verfügung gestellt.

Bedingungen für einen Vektor:

[1] Die Instruktion muss im (default) Vectortable Segment liegen (nur 
default, da BOOTRESET von Parametern abhängt).

[2] Die Instruktion muss entweder ein jmp oder ein rjmp sein

[3] Die Instruktion darf nicht zwischen zwei Vektoren liegen, falls ein 
Vektor 32-bit breit ist.

[4] Es muss mindestens einmal ein "sei" im disassembly vorkommen, da man 
sonst davon ausgehen muss, dass Interrupts gesperrt bleiben.

Eine zukünftige extra Bedingung wird sich die verwendeten SFRs anschauen 
um festzustellen, ob für den potentiellen Vektor die erforderlichen 
Konfigurationen vorgenommen werden.

Beispiel: Ausgeben von Interrupt Vektoren und deren ISR
------------------------------------------------------------
1
int main(const int argc, const char **argv) {
2
3
    /* ignoring checks for this example */
4
    vmcu_model_t  *m328p  = vmcu_model_ctor(VMCU_M328P); 
5
    vmcu_report_t *report = vmcu_analyze_ihex("file.hex", m328p);
6
7
    for(int32_t i = 0; i < report->n_vector; i++) {
8
9
        vmcu_vector_t *vect = &report->vector[i];
10
        vmcu_instr_t  *isr  = vect->xto->i;
11
        
12
        printf("Vector ID %d @ 0x%04x\n", vect->id, vect->addr);
13
        printf(" interrupt service routine at 0x%04x", isr->addr);
14
        printf("\n\n");
15
    }
16
    
17
    vmcu_report_dtor(report);
18
    vmcu_model_dtor(m328p);
19
    
20
    return EXIT_SUCCESS;
21
}

Output (Ausschnitt)
-----------------------
1
Vector ID 16 @ 0x0020
2
 interrupt service routine at 0x03f5
3
4
Vector ID 17 @ 0x0022
5
 interrupt service routine at 0x008a
6
7
Vector ID 18 @ 0x0024
8
 interrupt service routine at 0x03c3
9
10
Vector ID 19 @ 0x0026
11
 interrupt service routine at 0x039d

von David M. (milo-d)


Lesenswert?

Update v.0.8.6

-> Submodul für String Analyse wurde hinzugefügt. Die im binary 
vorhandenen Strings werden in die report structure abgelegt. Zurzeit nur 
ASCII encoding, jedoch eventuell bald auch UTF16.

-> Dabei besteht die string structure (vmcu_string_t) aus einer 
Stringadresse (Startadresse im FLASH), einer Stringlänge und einem 
Zeichenbuffer der den String selbst repräsentiert. Eine Folge an bytes 
wird als String erkannt, wenn sie mindestens 2 Zeichen lang ist (+ null 
byte).

-> Der Nutzer muss non printable characters mittels einer kleinen 
Hilfsfunktion selber handlen.

-> Ein kleiner Fehler im intel hex reader wurde behoben.

ASCII strings aus einer binary lesen
------------------------------------
1
int main(const int argc, const char **argv) {
2
3
    /* ignoring checks for this example */
4
    vmcu_model_t  *m328p  = vmcu_model_ctor(VMCU_DEVICE_M328P); 
5
    vmcu_report_t *report = vmcu_analyze_ihex("file.hex", m328p);
6
7
    for(int32_t i = 0; i < report->n_string; i++) {
8
9
        vmcu_string_t *str = &report->string[i];
10
11
        printf("Found string \"%s", str->bytes);
12
        printf("\" l = %d", str->length);
13
        printf(" @ 0x%04x\n", str->addr);
14
    }
15
    
16
    printf("\nTotal strings found: %d\n", report->n_string);
17
18
    vmcu_report_dtor(report);
19
    vmcu_model_dtor(m328p);
20
    
21
    return EXIT_SUCCESS;
22
}
1
Found string "Welcome " l = 8 @ 0x092e
2
Found string "[1] Login\n" l = 11 @ 0x0933
3
Found string "[2] Memory management\n" l = 23 @ 0x0939
4
Found string "Please authenticate yourself with your hardware token\n" l = 55 @ 0x0946
5
Found string "Please insert token. (%d characters)\n" l = 38 @ 0x0962
6
Found string "Token can only contain the characters [A-Z/a-z/0-9]\n" l = 53 @ 0x0975
7
8
Total strings found: 6

: Bearbeitet durch User
von David M. (milo-d)


Lesenswert?

v.0.8.8

-> vmcu_mnemonic_t structure zu vmcu_instr_t hinzugefügt. Diese Struktur 
löst den "char *mnem" member ab. vmcu_mnemonic_t enthält 4 strings:

  - base (z.B. "ldi", "movw", etc.)
  - src (z.B. "r29", "0xff", etc.)
  - dest ("r29", "0xff", etc.)
  - commment (z.B. "; r21 <- 0xab", "IF <- 0", etc.)

-> disasm driver hinzugefügt. Dies ist ein kleiner disassembler mit 
Syntax Highlighting, der die Vorteile der obigen Änderungen nutzt.

-> Außerdem wurde der interne disassembler überarbeitet. Diese 
Überarbeitung war eine notwendige Anpassung an die obigen Änderungen.

-> Bestehende driver und die README wurden angepasst.

Dieses Update macht die Ausgabe vom disassembly viel flexibler. 
Beispielsweise kann nun zwischen AT&T und Intel Syntax gewechselt werden 
oder ganz bestimmte Teile können sehr einfach mit Syntax Highlighting 
hervorgehoben werden.

Der Nachteil ist der, dass nun eine zusätzliche printer Fuktion 
geschrieben werden muss um das disassembly auszugeben.

Diese Funktion könnte so aussehen (intel syntax)
-----------------------------
1
/* this snippet can be used to assemble and print an instruction */
2
3
void print_instruction(vmcu_instr_t *instr) {
4
5
    vmcu_mnemonic_t *mnem = &instr->mnem;
6
7
    printf("%s ",  mnem->base);
8
    printf("%s",   mnem->dest);
9
10
    if(instr->dest.type != VMCU_OP_NONE)
11
        printf(", ");
12
13
    printf("%s ",  mnem->src);
14
    printf("%s\n", mnem->comment);
15
}

Dies ist das neue Layout von vmcu_instr_t
-----------------------------
1
typedef struct vmcu_instr {
2
3
    struct {              ///< instruction core
4
5
        VMCU_IKEY key;    ///< instruction key (instruction identifier)
6
        VMCU_GROUP group; ///< instruction group
7
    };
8
9
    int opcode;           ///< 16-bit or 32-bit opcode
10
    int addr;
11
12
    bool exec;            ///< instruction executable ?
13
    bool dword;           ///< 32-bit instruction ?
14
15
    vmcu_operand_t src;   ///< source operand (right operand)
16
    vmcu_operand_t dest;  ///< destination operand (left operand)
17
18
    vmcu_mnemonic_t mnem; ///< disassembled mnemonic of instruction
19
20
} vmcu_instr_t;


Und das ist das Layout von vmcu_mnemonic_t
-----------------------------
1
typedef struct vmcu_mnemonic {
2
3
    char base    [7]; ///< mnemonic base string (ldi, sts, etc.)
4
5
    char src     [9]; ///< source operand string (r29, 0xff9a, etc.)
6
    char dest    [9]; ///< destination operand string (r29, 0xff9a, etc.)
7
8
    char comment[40]; ///< comment string (todo: make comments optional)
9
10
} vmcu_mnemonic_t;

Wie man sehen kann, nimmt der Kommentar den meisten Speicher innerhalb 
der Struktur ein. Um Speicher zu sparen könnte man mit bedingter 
Kompilierung anfangen um einzelne Komponenten ein- bzw. auszuschalten. 
Somit wären Kommentare optional und würden nur bei tatsächlichem Bedarf 
Speicher verbrauchen.

Feedback/Verbesserungsvorschläge sind natürlich gern gesehen :)

von David M. (milo-d)


Lesenswert?

v.0.8.9 - Überarbeitung von Operanden und Operandentypen

-> VMCU_OP wurde überarbeitet und in VMCU_OPTYPE umgenannt. VMCU_OPTYPE 
wurde nun nach Spezifikation 
(https://en.wikipedia.org/wiki/Atmel_AVR_instruction_set) erstellt (mit 
einigen wenigen Änderungen).

Die Operandentypen sehen nun wie folgt aus:

    - VMCU_OPTYPE_NONE = -1 (no operand, therefore no type)
    - VMCU_OPTYPE_R,        (register operand)
    - VMCU_OPTYPE_RP,       (registerpair operand)
    - VMCU_OPTYPE_X,        (x pointer operand)
    - VMCU_OPTYPE_Y,        (y pointer operand)
    - VMCU_OPTYPE_Z,        (z pointer operand)
    - VMCU_OPTYPE_B,        (bit number 0-7)
    - VMCU_OPTYPE_K4,       (4-bit immediate)
    - VMCU_OPTYPE_K6,       (6-bit immediate)
    - VMCU_OPTYPE_K8,       (8-bit immediate)
    - VMCU_OPTYPE_IO5,      (5-bit I/O address)
    - VMCU_OPTYPE_IO6,      (6-bit I/O address)
    - VMCU_OPTYPE_D7,       (7-bit data address)
    - VMCU_OPTYPE_D16,      (16-bit data address)
    - VMCU_OPTYPE_P22,      (22-bit program address)
    - VMCU_OPTYPE_S7,       (7-bit signed displacement, program memory)
    - VMCU_OPTYPE_S12       (12-bit signed displacement, program memory)

-> VMCU_REGISTER enumeration für die GPRs (r0-r31) hinzugefügt.

-> vmcu_registerpair_t structure hinzugefügt. Diese Structure 
repräsentiert ein gewöhnliches Registerpaar der Form Rh:Rl. Die Struktur 
besteht aus 2 VMCU_REGISTER Variablen.

-> vmcu_operand_t wurde ebenfalls überarbeitet. Es enthält immernoch ein 
Feld für den Operandentypen (VMCU_OPTYPE) und neuerdings eine C-Union um 
die verschiedenen Datentypen darzustellen.

Die Union enthält folgende Datentypen:
  - uint8_t k  for type = K4, K6, K8
  - uint8_t b  for type = B
  - uint8_t io for type = IO5, IO6
  - uint16_t d for type = D7, D16
  - uint32_t p for type = P22
  - int16_t s  for type = S7, S12

-> endloop driver (driver/endloop/) verbessert. Der endloop driver kann 
nun zwischen bedingten und unbedingten Endlosschleifen unterscheiden und 
ermittelt diese mittels statischer Analyse.

- Driver Code wurden an v.0.8.9 angepasst

vmcu_operand_t nach Überarbeitung
------------------------
1
typedef struct vmcu_operand {
2
3
    union {
4
5
        uint8_t k;
6
        uint8_t b;
7
        uint8_t io;
8
9
        uint16_t d;
10
        uint32_t p;
11
        int16_t  s;
12
13
        VMCU_REGISTER r;
14
        vmcu_registerpair_t rp;
15
    };
16
17
    VMCU_OPTYPE type;
18
19
} vmcu_operand_t;

VMCU_OP nach Überarbeitung
------------------------
1
typedef enum {
2
3
    VMCU_OPTYPE_NONE = -1,
4
5
    VMCU_OPTYPE_R,
6
    VMCU_OPTYPE_RP,
7
    VMCU_OPTYPE_X,
8
    VMCU_OPTYPE_Y,
9
    VMCU_OPTYPE_Z,
10
    VMCU_OPTYPE_B,
11
    VMCU_OPTYPE_K4,
12
    VMCU_OPTYPE_K6,
13
    VMCU_OPTYPE_K8,
14
    VMCU_OPTYPE_IO5,
15
    VMCU_OPTYPE_IO6,
16
    VMCU_OPTYPE_D7,
17
    VMCU_OPTYPE_D16,
18
    VMCU_OPTYPE_P22,
19
    VMCU_OPTYPE_S7,
20
    VMCU_OPTYPE_S12
21
22
} VMCU_OPTYPE;

VMCU_REGISTER enumeration
------------------------
1
typedef enum {
2
3
    VMCU_REGISTER_NONE = -1,
4
5
    VMCU_REGISTER_R0,
6
    VMCU_REGISTER_R1,
7
    VMCU_REGISTER_R2,
8
    VMCU_REGISTER_R3,
9
    VMCU_REGISTER_R4,
10
    VMCU_REGISTER_R5,
11
    VMCU_REGISTER_R6,
12
    VMCU_REGISTER_R7,
13
    VMCU_REGISTER_R8,
14
    VMCU_REGISTER_R9,
15
    VMCU_REGISTER_R10,
16
    VMCU_REGISTER_R11,
17
    VMCU_REGISTER_R12,
18
    VMCU_REGISTER_R13,
19
    VMCU_REGISTER_R14,
20
    VMCU_REGISTER_R15,
21
    VMCU_REGISTER_R16,
22
    VMCU_REGISTER_R17,
23
    VMCU_REGISTER_R18,
24
    VMCU_REGISTER_R19,
25
    VMCU_REGISTER_R20,
26
    VMCU_REGISTER_R21,
27
    VMCU_REGISTER_R22,
28
    VMCU_REGISTER_R23,
29
    VMCU_REGISTER_R24,
30
    VMCU_REGISTER_R25,
31
    VMCU_REGISTER_R26,
32
    VMCU_REGISTER_R27,
33
    VMCU_REGISTER_R28,
34
    VMCU_REGISTER_R29,
35
    VMCU_REGISTER_R30,
36
    VMCU_REGISTER_R31
37
38
} VMCU_REGISTER;

vmcu_registerpair_t structure
------------------------
1
typedef struct vmcu_registerpair {
2
3
    VMCU_REGISTER low;
4
    VMCU_REGISTER high;
5
6
} vmcu_registerpair_t;

von David M. (milo-d)


Lesenswert?

v.0.8.10

- Added: vmcu_access_t
  - vmcu_access_t holds bitfields (representing booleans)
  - following bitfields are available
    - registers (true if instr reads/writes gpr)
    - flash     (true if instr reads/writes flash)
    - stack     (true if instr reads/writes stack)
    - io        (true if instr reads/writes io segment)
    - ds        (true if instr reads/writes data segment)
    - sp        (true if instr reads/writes stack pointer)
    - pc        (true if instr reads/writes program counter)
    - c_flag    (true if instr reads/writes carry flag)
    - z_flag    (true if instr reads/writes zero flag)
    - n_flag    (true if instr reads/writes negative flag)
    - v_flag    (true if instr reads/writes overflow flag)
    - s_flag    (true if instr reads/writes sign flag)
    - h_flag    (true if instr reads/writes halfcarry flag)
    - t_flag    (true if instr reads/writes t flag)
    - i_flag    (true if instr reads/writes interrupt flag)

  - NOTE: Although (stack, io, registers ⊂ ds), (stack = true) or (io = 
true)
    or (registers = true) does not imply (ds = true).

- Added: annotator stage in pipeline
  - this stage annotates instructions by adding additional information 
about
    the instruction itself, like groups and explicit/implicit read/write 
access.

- Added: rwaccess driver (driver/rwaccess/)
- Adjusted drivers, etc.

Example
------------
1
/* A possible implementation of print_instruction can be found above */
2
3
int main(const int argc, const char **argv) {
4
5
    /* ignoring checks for this example */
6
    vmcu_model_t  *m328p  = vmcu_model_ctor(VMCU_DEVICE_M328P); 
7
    vmcu_report_t *report = vmcu_analyze_ihex("file.hex", m328p);
8
9
    for(int32_t i = 0; i < report->progsize; i++) {
10
11
        vmcu_instr_t *instr = &report->disassembly[i];
12
        
13
        if(instr->writes.c_flag == true)
14
            print_instruction(instr);
15
        
16
        if(instr->reads.c_flag == true)
17
            print_instruction(instr);
18
    }
19
    
20
    vmcu_report_dtor(report);
21
    vmcu_model_dtor(m328p);
22
    
23
    return EXIT_SUCCESS;
24
}

Possible Output
-------------------
1
subi r18, 0x00     ; r18 <- r18 - 0x00
2
adiw r29:r28, 0x1a ; r29:r28 <- r29:r28 + 0x1a
3
sbci r23, 0xff     ; r23 <- r23 - 0xff - CF
4
cpc r19, r17       ; r19 - r17 - CF

von David M. (milo-d)


Angehängte Dateien:

Lesenswert?

v.0.8.11 - Finally added the control flow graph (cfg)

libvmcu has now control flow graphs

- added: CFG - control flow graph
  - vmcu_cfg_t is the controlflow graph of libvmcu. It holds,
    - vmcu_cfg_t node*, a dynamic array of vmcu_node_t structures
    - node[0] is the entry node
      - a node consists of
        - vmcu_xref_t xto, a cross-reference to the corresponding 
instruction (xref-to)
        - vmcu_cfg_node_t *t, a node pointer for the true branch
        - vmcu_cfg_node_t *f, a node pointer for the false branch
    - int32_t used, a simple node counter, should be equal to 
report.progsize

- added: CFG example driver (driver/cfg/)

- using CMake now
- added syntax highlight for stepper driver



A controlflow graph will allow us to analyze and model functions, loops, 
cycles, ...

Printing control flow of a binary
------------------------------------
1
/* A possible implementation of print_instruction can be found in repository */
2
3
int main(const int argc, const char **argv) {
4
5
    /* ignoring checks for this example */
6
    vmcu_model_t  *m328p  = vmcu_model_ctor(VMCU_DEVICE_M328P);
7
    vmcu_report_t *report = vmcu_analyze_ihex("file.hex", m328p);
8
9
    for(int32_t i = 0; i < report->cfg->used; i++) {
10
11
        vmcu_cfg_node_t *node = &report->cfg->node[i];
12
        print_instruction(node->xto.i);
13
14
        if(node->t != NULL) {
15
16
            printf("true  -> ");
17
            print_instruction(node->t->xto.i);
18
        }
19
20
        if(node->f != NULL) {
21
22
            printf("false -> ");
23
            print_instruction(node->f->xto.i);
24
        }
25
26
        printf("\n");
27
    } 
28
29
    vmcu_report_dtor(report);
30
    vmcu_model_dtor(m328p);
31
32
    return EXIT_SUCCESS;
33
}

Example Output
--------------------
1
         0x0000  .... f1f3  breq -2         ; (ZF == 1): PC <- PC + -2 + 1
2
true  -> 0x3fff  .... 839a  sbi 0x10, 3     ; IO[0x10, 3] <- 1
3
false -> 0x0001  .... 0fef  ldi r16, 0xff   ; r16 <- 0xff
4
--------------------------------------------------------------------------------
5
         0x0001  .... 0fef  ldi r16, 0xff    ; r16 <- 0xff
6
true  -> 0x0002  .... 5817  cp r21, r24     ; r21 - r24
7
--------------------------------------------------------------------------------
8
         0x0002  .... 5817  cp r21, r24     ; r21 - r24
9
true  -> 0x0003  .... 19f4  brne 3          ; (ZF == 0): PC <- PC + 3 + 1
10
--------------------------------------------------------------------------------
11
         0x0003  .... 19f4  brne 3          ; (ZF == 0): PC <- PC + 3 + 1
12
true  -> 0x0007  .... 0127  eor r16, r17    ; r16 <- r16 ^ r17
13
false -> 0x0004  .... a895  wdr             ; watchdog reset
14
--------------------------------------------------------------------------------
15
         0x0004  .... a895  wdr             ; watchdog reset
16
true  -> 0x0005  .... 8895  sleep           ; circuit sleep
17
--------------------------------------------------------------------------------
18
         0x0005  .... 8895  sleep           ; circuit sleep
19
true  -> 0x0006  .... 0000  nop             ; no operation
20
--------------------------------------------------------------------------------
21
         0x0006  .... 0000  nop             ; no operation
22
true  -> 0x0007  .... 0127  eor r16, r17    ; r16 <- r16 ^ r17
23
--------------------------------------------------------------------------------
24
         0x0007  .... 0127  eor r16, r17    ; r16 <- r16 ^ r17
25
--------------------------------------------------------------------------------
26
         0x3fff  .... 839a  sbi 0x10, 3     ; IO[0x10, 3] <- 1
27
true  -> 0x0000  .... f1f3  breq -2         ; (ZF == 1): PC <- PC + -2 + 1
28
--------------------------------------------------------------------------------

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.