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 10 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 6 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
(Vor 6 Stunden)HenneNWH schrieb: Nur zu Info: Wenn sich an den "neueren Binaries" etwas ändert ist das in Ordnung.

ja das ist klar - ich wollte nur aufzeigen das ein ältere Kompiler vielleicht die Notation schon reicht um anderen Code zu erzeugen
so wie der gcc/clang wenn man die Optimierung ausschaltet - als Versuch Borland 3.1 Optimizer Qualität zu erreichen - im Vergleich :) - mehr Bedeutung hatte der Vergleich nicht

(Vor 6 Stunden)HenneNWH schrieb: So wie ich das wahrnehme sind MSVC und Delphi "nur noch" etablierte IDE's und Debugger.

MSVC mit Delphi in einen Topf zu werfen passt so gar nicht - Delphi(und C++Builder) sind faktisch tod und haben wirklich nur noch Nischenbedeutung

MSVC ist in der Windows-Welt der Platzhirsch - die meisten Windows-Programme werden immer noch mit dieser IDE und dem Kompiler erstellt
weil die IDE den besten(schnellsten) Debugger hat der so von keiner anderen IDE erreicht wird - nicht mal im Ansatz - Optimiert auf Mio/Mrd Symbole - davon ist leider jede Linux-IDE Kilometer entfernt - die meisten Spiele auf dem Markt werden damit erzeugt, Epic ist ja direkter Entwicklungspartner von Microsoft - nicht das ich ein Spieler bin - aber das ist ein Millarden-Markt der nicht klein oder unrelevant ist

Wenn man Windows-Software mit C++ macht ist die wahrscheinlichkeit auf MSVC zu treffen mind. 80%

Chrome wird mit dem clang-cl gemacht - damit google die MSVC-IDE nutzen kann aber darin mit dem clang bauen kann - das machen aber nach meinem Wissen nur noch LibreOffe - also nicht clang sondern ein clang-Kompiler der die Microsoft-Compiler Parameter versteht und sich auch so verhält - der kann direkt in MSVC verwendet werden - damit man den Debugger usw. hat - sonst macht der clang-cl keinen Sinn - clang-cl ist auch wie schon gesagt im Standard LLVM/Clang Paket immer mit dabei

gcc/clang kämpfen hier und da um optimaleren Code - mal gewinnt clang, mal gcc - wichtig die das die jemanden haben gegen den sie kämpfen können - der MSVC ist in vielen Fällen schlechter - wird dewegen aber nicht weniger verwendet weil es in vielen Programmen nicht die relevanz hat
Zitieren




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