Themabewertung:
  • 4 Bewertung(en) - 3.5 im Durchschnitt
  • 1
  • 2
  • 3
  • 4
  • 5
Reverse Engineering der NLT II
(13.11.2025, 02:41)siebenstreich schrieb: Jetzt sind es wieder 20 Bytes Unterschied, wie ich es vermutet hatte.

Am Offset 00:01:51:A4 steht
  • In der originalen SCHICKM.EXE: 0C:01:CA:07
  • In der am 12.11.2025 compilierten BLADEM.EXE: 0C:0B:E9:07
  • In der am 13.11.2025 compilierten BLADEM.EXE: 0D:0B:E9:07

Damit ist der Fall klar: Das ist das Datum im Format Tag (1 Byte), Monat (1 Byte), Jahr (2 Bytes little endian). Die originale SCHICKM.EXE wurde demzufolge am 12. Januar 1994 compiliert. (Warum 1994 und nicht 1992? -- Weil es sich um die deutlich später erschienene CD-Version handelt.)

Gestern hat also zufälligerweise der Tag des Monats mit dem vom originalen Compilierzeitpunkt übereingestimmt, womit wir nur 19 Bytes Differenz gesehen haben und nicht 20. Im Januar wird das den ganzen Monat lang so sein, und am 12. Januar sind es sogar nur 18 Bytes Differenz.

Hab gerade für's Linken die Uhr der DOSBox zurückgedreht.
Die Binäräquivalenz ist jetzt bei 17 (+2 für den Stackpointer).

Zu dem Typecast: Keine Ahnung, viele Wege führen nach Rom.
Zitieren
(Gestern, 16:39)HenneNWH schrieb:
(13.11.2025, 02:41)siebenstreich schrieb: Jetzt sind es wieder 20 Bytes Unterschied, wie ich es vermutet hatte.

Am Offset 00:01:51:A4 steht
  • In der originalen SCHICKM.EXE: 0C:01:CA:07
  • In der am 12.11.2025 compilierten BLADEM.EXE: 0C:0B:E9:07
  • In der am 13.11.2025 compilierten BLADEM.EXE: 0D:0B:E9:07

Damit ist der Fall klar: Das ist das Datum im Format Tag (1 Byte), Monat (1 Byte), Jahr (2 Bytes little endian). Die originale SCHICKM.EXE wurde demzufolge am 12. Januar 1994 compiliert. (Warum 1994 und nicht 1992? -- Weil es sich um die deutlich später erschienene CD-Version handelt.)

Gestern hat also zufälligerweise der Tag des Monats mit dem vom originalen Compilierzeitpunkt übereingestimmt, womit wir nur 19 Bytes Differenz gesehen haben und nicht 20. Im Januar wird das den ganzen Monat lang so sein, und am 12. Januar sind es sogar nur 18 Bytes Differenz.

Hab gerade für's Linken die Uhr der DOSBox zurückgedreht.



Die Binäräquivalenz ist jetzt bei 17 (+2 für den Stackpointer).

Zu dem Typecast: Keine Ahnung, viele Wege führen nach Rom.


Coole Sache! (Und vielen Dank, dass du mich in dem commit-comment und dem @REM erwähnst...)

Auch interessant, wie du das machst. So wie ich es verstehe, ist es ein kleines C-Programm, das die Uhr der Dosbox auf den 12.1.1994 einstellt, und welches du vor dem Ausführen des Borland-Linkers in der Dosbox startest.

(Das ganze erinnert mich daran, dass meine Eltern sich gelegentlich über eine verstellte Uhrzeit auf dem damaligen DOS-PC geärgert hatten. Der Schuldige war das Spiel Sokoban, bzw. ich, der es gespielt hatte. Vermutlich war das eine etwas brachiale Methode, um im Spiel die Zeit zu messen.)
Zitieren
Zitat:Die Binäräquivalenz ist jetzt bei 17 (+2 für den Stackpointer).

Stackpointer kannst du ja auch einfach patchen - Borland definiert seinen stack ja eh dynamisch (soweit ich gelesen habe)
Zitieren
(Gestern, 15:49)siebenstreich schrieb: bereits vom Typ (struct enemy_flags) sein. Der Typecast hat also eigentlich nichts mehr zu tun -- warum ändert der Typecast den erzeugten Bytecode?

weil das durch den unnötigen cast eine copy forciert werden könnte - vielleicht nur beim Borland C++ 3.1 - neue kompiler machen sowas nicht, da kommt der gleiche code raus

aber selbst ein aktueller gcc erzeugt für die beiden Zugriffe anderen Code bei ohne Optimierung -O0 -> https://gcc.godbolt.org/z/z5vqa3szW
Zitieren
(Gestern, 16:39)HenneNWH schrieb: Hab gerade für's Linken die Uhr der DOSBox zurückgedreht.
Die Binäräquivalenz ist jetzt bei 17 (+2 für den Stackpointer).

vielleicht würde es, wenn der Linker wirklich BLADEM.EXE verwendet es sich weiter angleichen, Checksummen oder sowas über den Dateiname, ... aber der Ist-Zustand ist ja schon ok
Zitieren
ist im seg090.cpp Zeile 453 wirklich die \0 am Ende vom String nötig - die null wird ja normalerweise automatisch eingefügt und clang beschwert sich
Zitieren
(Gestern, 17:22)llm schrieb:
(Gestern, 15:49)siebenstreich schrieb: bereits vom Typ (struct enemy_flags) sein. Der Typecast hat also eigentlich nichts mehr zu tun -- warum ändert der Typecast den erzeugten Bytecode?



weil das durch den unnötigen cast eine copy forciert werden könnte - vielleicht nur beim Borland C++ 3.1 - neue kompiler machen sowas nicht, da kommt der gleiche code raus



aber selbst ein aktueller gcc erzeugt für die beiden Zugriffe anderen Code bei ohne Optimierung -O0 -> https://gcc.godbolt.org/z/z5vqa3szW
Danke für die Erklärung. Der Typecast führt also dazu, dass eine Kopie angelegt wird.
Zitieren
(Vor 8 Stunden)llm schrieb: ist im seg090.cpp Zeile 453 wirklich die \0 am Ende vom String nötig - die null wird ja normalerweise automatisch eingefügt und clang beschwert sich


Die Binäräquivalenz hilft uns hier nicht weiter, weil sich die Code-Stelle in einem FEATURE_MOD befindet. Ich habe mal verglichen, wie sich die resultierenden schick_gcc Dateien verändern, wenn ich das \0 rausnehme. Sie sind nicht identisch, d.h. das \0 hat tatsächlich eine Einfluss.

Man müsste sich die erzeugte Textmeldung also mal konkret im Spiel anschauen, um zu sehen, ob es das \0 wirklich braucht.

EDIT: Der Meister hat es schon erledigt!
Zitieren
Es gibt an vielen Stellen Variablen, die in mehrfacher Funktion verwendet werden (z.B. als inventar-Slot und später in der Funktion als Helden-Position). Ich frage mich, wie wir solche Variablen benennen sollen. Ich bin dazu übergegangen, sie "tmp" zu nennen (bzw. "tmp_1", "tmp_2" usw wenn es mehrere sind), und hinter die Deklaration einen Kommentar im Stil von "/* multi use: inv_slot, hero_pos */" zu schreiben.

Findet ihr das gut? Gib es eine bessere Bezeichnung als 'tmp"? Wo ich gerade nochmals darüber nachdenke, fällt mir als Bezeichner noch "multiuse" ein, das wäre noch deutlicher.
Zitieren
@LLM & siebenstreich:

* Das überflüssige Zeichen '\0' wurde aus dem String entfernt. Strings haben die abschließende 0 "automatisch".
  Wenn man einen C-String als Char-Array schreibt, ist die abschließende '\0' per Hand einzufügen.

*  Um schnellere Programme zu erhalten, haben die Compilerbauer damals einen Trick benutzt:
    Bsp: Zugriff auf Element array[i - 10].

    Normalerweise würde man erst (i - 10) berechnen, diesen Wert mit der Größe eines Elements multiplizieren, die Startadresse des Arrays addieren und dann den Wert aus dem Array auslesen.

    Da zur Compilezeit schon Informationen vorhanden sind, kann die Adresse von array[-10] schon im Voraus berechnet werden.
    Anschließend wird i * Elementgröße berechnet und zur Adresse von array[-10] addiert und der Wert ausgelesen.
    Nutzen: Die Berechnung von (i - 10) wurde eingespart.
    Nachteil: Es tauchen Speicheradressen im Binary auf, welche NICHT den Anfangsadressen von Arrays entsprechen.

    So war das früher!

  Unter dem Link von LLM sieht der Assemblerkundige an der Ausgabe von Clang, dass bei test1() direkt der Wert ausgelesen wird.
  Bei test2() werden die Flags in einer lokalen Variable zwischengespeichert.

  Stellt man auf den GCC um, wird bei test1() eine Adresse (x + 48) berechnet und der Wert mit dem Offset +4 ausgelesen.
  Bei test2() wird eine Adresse (x + 52) berechnet und der Wert ohne Offset ausgelesen.

  Test: (x + 48 + 4) ?= (x + 52) => (x + 52) == (x + 52) => PASST!

Nur zu Info: Wenn sich an den "neueren Binaries" etwas ändert ist das in Ordnung.
Zu GCC habe ich sehr großes Vertrauen, da dieser Compiler seit 1987 zum Bauen sämtlicher Linux-Distributionen benutzt wird.
Im Gegensatz zum BCC wird der allerdings noch aktiv weiterentwickelt.

Clang/LLVM ist seit mindestens 2000 verfügbar, kann Aufgrund des neugedachten Ansatzes wesentlich besser optimieren.
Da Clang mittlerweile auch Einzug in die Produkte der vermeintlich Großen gehalten hat (M$, Embarcadero) ist klar,
dass in diesen Unternehmen bzgl. der Weiterentwicklung von Compilern Stillstand herrscht.
So wie ich das wahrnehme sind MSVC und Delphi "nur noch" etablierte IDE's und Debugger.

Benchmark: Phoronix Clang vs. GCC
Fehlermeldungen: Clang, GCC, MSVC

@siebenstreich: Mehrfach benutzte Variablen würde ich jetzt noch nicht umbenennen. Der richtige Weg wäre diese später aufzusplitten, den Scope zu reduzieren und sie dann richtig zu benennen.
Zitieren
(Vor 4 Stunden)HenneNWH schrieb: @siebenstreich: Mehrfach benutzte Variablen würde ich jetzt noch nicht umbenennen. Der richtige Weg wäre diese später aufzusplitten, den Scope zu reduzieren und sie dann richtig zu benennen.

Ich denke mir halt, alles was man kapiert hat kann man auch gleich im Programmcode kenntlich machen. Dass das später in scopes zerlegt wird, widerspricht dem ja nicht.
Jetzt haben wir den Binäräquivalenz-Check noch zur Verfügung, der uns vor Fehlern bewahrt!
Zitieren
Das verstehe ich. Im aktuellen Zustand sorgt das für Frustration.
Aktuell mache ich an schlecht benannte, mehrfach benutze Variablen einen Kommentar dran.
Die Variablen, die "nur schlecht benannt" sind, werden gleich umbenannt.

Code:
seg058.cpp:    signed int i; /* REMARK: also used as handle */
Zitieren




Benutzer, die gerade dieses Thema anschauen: 21 Gast/Gäste