Forum: PC-Programmierung Linux x86_64: Speicherzugriff in Software abfangen und auf Funktion umleiten.


von M. Н. (Gast)


Lesenswert?

Guten Tag,

ich habe heute mal eine etwas speziellere Frage. Es geht darum den Code 
für einen Mikrocontroller auf dem PC zu testen. Dieser Code ruft direkt 
Register auf. Es wird also direkt an Speicheradressen geschrieben.

Der Code soll auf dem PC getestet werden. Vom Hardwaremodell existiert 
ein Modell, das die verschiedenen Register über write/read funktionen 
zur Verfügung stellt.

Betriebssystem ist Linux auf x86_64 (64 bit binary).

Ich möchte nun den Speicherzugriff des Testprogramms abfangen und auf 
die Funktionen umleiten.

dabei brauche ich nun etwas Hilfe und Anregungen. Ich hoffe jemand ist 
in diesen Dingen bewandert :)

Ich stelle mir das Ganze so vor:

Ich lasse mir über mmap() eine Page geben. Bei dieser blocke ich mittels 
mprotect() Lese- und Schreibzugriffe.
Greife ich nun Auf eine Adresse in der Page zu, löst das eine 
SIGSEGV-Exception aus. Diese Exception kann ich in meiner SW abfangen.

Ich muss jetzt irgendwie im Handler von SIGSEGV rausbekommen, was der 
Zugriff war und die entsprechende Funktion meines Hardware-Modells 
aufrufen.

Hierbei sehe ich folgende Probleme:
* Wie bekomme ich Adresse und bei Schreiboperationen den Wert
* Wie Liefere ich gelesene Werte zurück

Natürlich wäre es bestimmt möglich im Handler den OP-Code des Befehs zu 
parsen und sich daraus die verwendeten Register zusammenzusuchen. Diese 
sollten im mitgelieferten Context im Handler verfügbar sein. Allerdings 
bin ich mit amd64/x86 nicht so wirklich fit und habe keine Ahnung, wie 
ich das Ganze implementieren könnte.

Zweiter Ansatz:
Ich habe auch irgendwo im Internet gelesen, dass man eventuell im 
SIGSEGV handler die Page wieder freigeben kann, sodass der Zugriff 
erfolgt und dann eine Trap setzt, die sofort nach der Beendigung der 
Operation wieder einen Handler aufruft, in dem dann z.B. der 
geschriebene Wert gelesen werden kann. Danach wird die Page wieder 
blockiert.

Das Ganze muss nicht Thread-safe sein. Die Applikation läuft nur auf 
einem Thread.

Kennt sich jemand mit der Materie aus und kann mich in die richtige 
Richtung schubsen?

von Dr. Sommer (Gast)


Lesenswert?

Wie willst du denn Zugriffe auf "Peripherie"-Adressen von normalen 
Zugriffen auf Variablen, Funktionen unterscheiden?

Kapsele doch lieber alle Peripherie-Zugriffe in ein Makro/Funktion. Zum 
Simulieren änderst du das dann ab.

von Vincent H. (vinci)


Lesenswert?

Ich versteh irgwendwie nicht ganz was du vorhast?
Du kannst doch irgendeinen Speicherbereich nehmen und schaun ob dort 
hineingeschrieben wird?

Ob du jetzt auf 0x08002000 oder irgendeine vom OS zugewiesene Stelle 
schreibst ist doch vollkommen irrelevant?

von Uhu U. (uhu)


Lesenswert?

Unter man mprotect steht ein Beispiel…

von Rolf M. (rmagnus)


Lesenswert?

Ich würde mir mal anschauen, wie bei gdb Break- und Watchpoints 
umgesetzt sind. Die machen ja genau das.

von DPA (Gast)


Lesenswert?

Es gäbe noch userfaultfd:
http://man7.org/linux/man-pages/man2/userfaultfd.2.html

Aber egal was man macht, man muss sich immer um die ganze Page kümmern, 
andernfalls kommt man um das Parsen der Instruktion nicht herum. Ausseer 
vielleicht mit libgdb. Aber was auch immer du vor hast, so wird das 
vermutlich nichts.



Kompilierst du das UC Projekt zum Testen für den PC? Dann musst du eben 
beim Setzen von Registern den Code so schreiben, dass du beim PC ne 
Funktion aufrufen kannst, die dann den Rest simuliert. Am besten trennt 
man den HW-Spezifischen Part in solchen fällen aber und ersetzt den beim 
PC mit einem dort geeigneten.

Oder emulierst du das Programm auf dem PC nur? Dann Fang den Fall doch 
einfach im Emulator ab.

von Daniel (Gast)


Lesenswert?

Steht die Adresse nicht im si_addr Feld der siginfo_t Struktur?
Um das Parsen der Instruktion um herauszufinden ob es ein Lese- oder 
Schreibzugriff war und welcher Wert geschrieben wurde, kommst du aber 
nicht herum.

von Egon D. (Gast)


Lesenswert?

M. H. schrieb:

> Der Code soll auf dem PC getestet werden.

Dafür verwendet man vernünftigerweise einen
Emulator.

von M. Н. (Gast)


Lesenswert?

Dr. Sommer schrieb:
> Wie willst du denn Zugriffe auf "Peripherie"-Adressen von normalen
> Zugriffen auf Variablen, Funktionen unterscheiden?

Indem ich die Basisadresse meines Moduls in die geblockte Page lege.
Ich blocke ja nicht den gesamten Speicher ab. Nur eine Page / evtl 
mehrere Pages.

Egon D. schrieb:
> Dafür verwendet man vernünftigerweise einen
> Emulator.

Bringt nur nichts, wenn es die Hardware nicht im Emulator gibt.

Vincent H. schrieb:
> Ob du jetzt auf 0x08002000 oder irgendeine vom OS zugewiesene Stelle
> schreibst ist doch vollkommen irrelevant?

Nein. Ich möchte, dass beim Lesen einer Speicheradresse, eine 
reg_read(uint32_t addr) Funktion aufgerufen wird in meinem 
Simulationsmodell. Der Rückgabewert wird dann dem Lesebefehl auf die 
Speicheradresse untergejubelt.

Ich weiß auch, dass das auf einem amd64 Prozessor geht. Dachte nur, dass 
jemand eventuell da fit ist und sofort weiß, wie's geht.

von Carl D. (jcw2)


Lesenswert?

M. H. schrieb:
>
> Nein. Ich möchte, dass beim Lesen einer Speicheradresse, eine
> reg_read(uint32_t addr) Funktion aufgerufen wird in meinem
> Simulationsmodell. Der Rückgabewert wird dann dem Lesebefehl auf die
> Speicheradresse untergejubelt.

Und warum muß der Call von reg_read/reg_write mit allen zur Verfügung 
stehenden Bremsen ausgeführt werde?
Warum nicht einfach diese gleich direkt anspringen?
Der zeitliche Unterschied zum direkten Speicherzugriff kann es ja nicht 
sein, denn die paar Befehle mehr für den Call willst du ja durch ein 
paar tausend für die Signalverarbeitung ersetzen.

von M. Н. (Gast)


Angehängte Dateien:

Lesenswert?

Habe das Ganze nun erfolgreich implementiert. Damit ihr auch etwas davon 
habt, hänge ich meinen grausamen C Code an. Er kompiliert. Aber gerade 
so. Kann man noch deutlich schöner machen. Ist mehr so ein proof of 
concept.

Die Funktionen write_memory und read_memory werden nun bei jedem Zugriff 
als Callbacks verwendet. Das Verhalten ist zum testen so implementiert, 
dass die lesefunktion unabhängig von der Adresse immer einen 
inkrementierenden Wert zurückliefert. Die Schreibefunktion nutzt ein 
printf, um den Zugriff zu signalisieren.

Der Code
1
printf("RD1: %d\n", *page);
2
printf("RD2: %d\n", *page);
3
printf("RD3: %d\n", *page);
4
5
*page = 100;
führt somit zur Ausgabe:

RD1: 1
RD2: 2
RD3: 3
Write @ <Adresse>: 100

von Daniel A. (daniel-a)


Lesenswert?

Und was machst du, wenn mal 8 statt 4 bytes gelesen werden?

von M. Н. (Gast)


Lesenswert?

Daniel A. schrieb:
> Und was machst du, wenn mal 8 statt 4 bytes gelesen werden?

Passiert bei mir nicht :)
Aber man kann den Code ja gegebenenfalls umbauen.

von Da D. (dieter)


Lesenswert?

Ich verstehe noch nicht ganz, warum man da eine so komplizierte Lösung 
braucht. Wir machen auch Unittests für Controllercode auf dem PC. Es 
gibt einfach keine direkte Speicherzugriffe im eigentlichen Programm, 
sondern nur Zugriffe über eine read und write Funktion. Diese Funktionen 
werden für die Unittests durch eine Mock-Funktion ersetzt, die die 
Zugriffe auf das Hardwaremodell umlenkt. Fertig ist die Laube. Die 
Lösung ist maximal trivial, läuft unter jedem Betriebsystem, auf jedem 
Prozessor, ist maximal schnell, etc.

Technisch gesehen ist deine Lösung auf jeden Fall sehr interessant. Aber 
ist das jetzt nur Spieltrieb, oder gibt es noch weitere Vorteile?

von M. Н. (Gast)


Lesenswert?

Da D. schrieb:
> Ich verstehe noch nicht ganz, warum man da eine so komplizierte Lösung
> braucht.

Das sind keine Unit-Tests. Die SW ist eine fertige Software, die auf 
echter Hardware Probleme macht.
Auf diese Weise muss ich nicht hunderte C Files anpassen. Klar hätte ich 
den Code ändern können. Aber dann wäre ich da bestimmt ne Woche dran. 
Außerdem läuft die Simulation mit dem Hardwaremodell im Hintergrund 
sowieso ewig. Ob das jetzt 8 Tage oder 9 dauert ist dann auch egal. Vor 
Weihnachten habe ich das Ergebnis eh nicht. Aber so habe ich jetzt ne 
halbe Stunde investiert und mich an meine Vorlesung zum x86 erinnert, 
anstatt bis Weihnachten nur Code zu schaufeln. Außerdem ist nicht 
gesagt, dass ich beim umcoden nicht andere Fehler einabaue, oder der 
Fehler eventuell verschwindet.

von Da D. (dieter)


Lesenswert?

Achso, die Testmethode soll nachträglich auf eine große Menge 
bestehendem Code angewendet werden. Dann verstehe ich warum du diesen 
Ansatz verfolgst. Danke!

von Daniel (Gast)


Lesenswert?

Nette Idee mit dem Single Stepping.
Aber was wenn der Compiler eine Instruktion generiert, die die 
Speicherstelle sowohl liest als auch beschreibt, z.B. um ein Bit zu 
setzen?

von Uhu U. (uhu)


Lesenswert?

Die Idee ist nur in der Theorie schön: der Programmablauf verlangsamt 
sich dadurch ganz drastisch.

von Martin S. (strubi)


Lesenswert?

Moin,

ev. obsoletes Echo, aber ich würde solche Tests für "don't 
touch"-Software eher im qemu machen. Da lässt sich die Hardware relativ 
vernünftig modellieren.

von M. Н. (Gast)


Lesenswert?

Uhu U. schrieb:
> Die Idee ist nur in der Theorie schön: der Programmablauf verlangsamt
> sich dadurch ganz drastisch.

Ich habe nicht behauptet, dass das eine schnelle Lösung ist. Diese 
ganzen Exceptions sind langsam. Das steht außer Frage

Martin S. schrieb:
> ev. obsoletes Echo, aber ich würde solche Tests für "don't
> touch"-Software eher im qemu machen. Da lässt sich die Hardware relativ
> vernünftig modellieren.

Ich weiß nicht, wie einfach es in qemu ist, aber ich denke, dass es 
höchstwahrscheinlich aufwendiger ist, ein über viele Jahre gewachsenes 
System-C Modell da anzuflanschen.

von Martin S. (strubi)


Lesenswert?

>
> Ich weiß nicht, wie einfach es in qemu ist, aber ich denke, dass es
> höchstwahrscheinlich aufwendiger ist, ein über viele Jahre gewachsenes
> System-C Modell da anzuflanschen.

Keine Ahnung, damit hab ich mich nicht beschäftigt. Habe nur mal ein ( 
komplexeres) VHDL-Modell damit verkabelt. Das mühsame daran war das 
Interface (Shared Memory) und das asynchrone versus synchrone Timing der 
HW vernünftig zu verwursten. Ansonsten war der qemu code gut zu lesen.

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.