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?
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.
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?
Unter man mprotect steht ein Beispiel…
Ich würde mir mal anschauen, wie bei gdb Break- und Watchpoints umgesetzt sind. Die machen ja genau das.
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.
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.
M. H. schrieb: > Der Code soll auf dem PC getestet werden. Dafür verwendet man vernünftigerweise einen Emulator.
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.
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.
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
Und was machst du, wenn mal 8 statt 4 bytes gelesen werden?
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.
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?
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.
Achso, die Testmethode soll nachträglich auf eine große Menge bestehendem Code angewendet werden. Dann verstehe ich warum du diesen Ansatz verfolgst. Danke!
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?
Die Idee ist nur in der Theorie schön: der Programmablauf verlangsamt sich dadurch ganz drastisch.
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.
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.
> > 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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.