Seite 1 von 1

CPP, Zugriff bzw. überladen von "->" Member Access Operators

Verfasst: 06.10.2021, 10:51
von Walker
ich kämpfe mit dem Überladen von "->" . Hintergrund dafür ist das ich mitbekommen möchte wer in dem sehr vielen Legacy-Code (virtuelle) Methoden aufruft. Ich habe nur Zugriff auf die Basisklasse. Die Basisklasse ist gut 20 Jahre alt , also um Designen alter Strukturen ist nicht möglich.

Ich hätte erwartet das alle Situationen einen Log auswerfen, aber nur zwei der Zugriffe loggen.:

Code: Alles auswählen

	pOB->a = 22; 
	pOB->b = 345;
	pOB->c = 22345;
        pOB->Logtest();//Log access
       (*pOB).a = 3;
       (*pOB)->b = 7; //C access 

Hier das Beispiel:
https://godbolt.org/z/rsvT3v5s6

Code: Alles auswählen

#include <iostream>
#include <cstdio>
using namespace std;

class COperTest
{
public:
	long a,b;
    static long c;

	COperTest()
	{
		a=5;
		b=7;
	}
	COperTest & operator *()
	{
		printf("A access %d\n",__LINE__);
		return *this;
	}
	COperTest * operator->() const
	{
		printf("B access %d\n",__LINE__);
		return  const_cast<COperTest*>(this);
	}
	COperTest * operator->()
	{
		printf("C access %d\n",__LINE__);
		return this;
	}
    void Logtest()
    {
        printf("Log access %d\n",__LINE__);
    }

};

long COperTest::c = 33;


void Test()
{
    printf("Test %d\n",__LINE__); //Test 
	COperTest op;
	COperTest *pOB = &op;
	pOB->a = 22; 
	pOB->b = 345;
	pOB->c = 22345;
    pOB->Logtest();//Log access
    (*pOB).a = 3;
    (*pOB)->b = 7; //C access 


}



int main()
{
    cout<<"Hello World" <<endl;
    Test();
    printf("\n\nReady\n");
    return 0;
}
Über sachdienliche Hinweise würde ich mich freuen.

Re: CPP, Zugriff bzw. überladen von "->" Member Access Operators

Verfasst: 07.10.2021, 00:04
von DerAlbi
Ich weiß, du willst eine Programmier-Lösung, aber: setze einen Daten-Breakpoint auf die vtable der Klasse...?

Re: CPP, Zugriff bzw. überladen von "->" Member Access Operators

Verfasst: 07.10.2021, 00:37
von Krishty
Funktionieren Daten-Breakpoints auch für bloße Zugriffe, statt nur für Veränderung?!

Re: CPP, Zugriff bzw. überladen von "->" Member Access Operators

Verfasst: 07.10.2021, 02:07
von DerAlbi
Wie sau. Das ist Hardware-Unterstützt von der CPU. Für den GDB gibt es z.B. "rwatch" für lesend-zugriff und "awatch" für r/w-zugriff. Andere Debugger können das sicher auch.
Man muss nur die vtable-Adresse rausfinden, das sagt einem aber auch der Debugger. Wenn man wirklich ein Log haben will, könnte man die vtable hacken/umleiten und von der injizierten Funktion aus die Originalfunktion aufrufen, nachdem man fein geloggt hat.
Man hätte dann sogar direkt den Call-Ursprung irgendwo in den Registern stehen (oder im Stack?) - wie man die Code-Adresse in Datei und Zeilennummer umwandelt.... hmmh.
Und wie man in read-only-Speicher rumschreibt... weiß hier sicher jemand, stimmts, Krishty?

Re: CPP, Zugriff bzw. überladen von "->" Member Access Operators

Verfasst: 07.10.2021, 05:49
von Mirror
Ich glaube das Verhalten ist korrekt. Probiere mal den "->" global zu überladen. Ich habe es zwar nicht ausprobiert, aber einen Versuch wäre es wert.

Re: CPP, Zugriff bzw. überladen von "->" Member Access Operators

Verfasst: 07.10.2021, 07:30
von Alexander Kornrumpf
Mirror hat geschrieben: 07.10.2021, 05:49 Ich glaube das Verhalten ist korrekt.
Es ist zumindest in sich logisch.

Nur mal kurz für das gemeinsame Verständnis, die Zeile die tatsächlich loggt ruft operator -> auf COperTest auf, die anderen Zeilen rufen es auf COperTest* auf. Überladen ist nur ersteres. Korrekt?

Re: CPP, Zugriff bzw. überladen von "->" Member Access Operators

Verfasst: 07.10.2021, 08:22
von Mirror
Alexander Kornrumpf hat geschrieben:Überladen ist nur ersteres.
Genau das habe ich gemeint. Mit überladen für den globalen Scope meine ich so wie man einen Vektor negiert.

COperTest * operator -> ( COperTest *pObject ) // oder ( CoperTest * & rObject )
{
return pObject;
}

Ist frei von der Leber geschrieben, kann also noch Buggy sein !!! Aber als Ansatzpunkt ...

Re: CPP, Zugriff bzw. überladen von "->" Member Access Operators

Verfasst: 07.10.2021, 09:18
von Walker
Mirror hat geschrieben: 07.10.2021, 08:22
Alexander Kornrumpf hat geschrieben:Überladen ist nur ersteres.
Genau das habe ich gemeint. Mit überladen für den globalen Scope meine ich so wie man einen Vektor negiert.

COperTest * operator -> ( COperTest *pObject ) // oder ( CoperTest * & rObject )
{
return pObject;
}

Ist frei von der Leber geschrieben, kann also noch Buggy sein !!! Aber als Ansatzpunkt ...
Grob ist die Fehlermeldung bei beiden Varianten:
error: overloaded 'operator->' must have at least one parameter of class or enumeration type

Re: CPP, Zugriff bzw. überladen von "->" Member Access Operators

Verfasst: 07.10.2021, 09:34
von Mirror
Habe es eben auch ausprobiert. Bei mir meckert er "operator -> muss ein nicht statischer Member sein". Geht also nicht. Naja, wäre zumindest im Bereich des möglichen gewesen

Re: CPP, Zugriff bzw. überladen von "->" Member Access Operators

Verfasst: 07.10.2021, 09:35
von Schrompf
Wenn ich das Godbolt-Beispiel richtig lese, fehlt Dir der 'B'-Text? Und der kann doch gar nicht kommen, weil der im const-Overload des Pfeil-Operators kommt. Und der wird nicht aufgerufen, Du machst -> nur auf nicht-konstanten Objekten.

Re: CPP, Zugriff bzw. überladen von "->" Member Access Operators

Verfasst: 07.10.2021, 09:43
von Walker
Im Prinzip überlade ich den "->" Operator oben für die Klasse COperTest so das folgendes dann geht:
COperTest xyz;
xyz->a = 5;

Das würde auch den Zugriff loggen. Aber der Build-In oder Internal Operator -> des Pointers von COperTest kann ich wohl nicht überladen.

Re: CPP, Zugriff bzw. überladen von "->" Member Access Operators

Verfasst: 07.10.2021, 09:56
von Schrompf
Ahso. Stimmt.

Re: CPP, Zugriff bzw. überladen von "->" Member Access Operators

Verfasst: 07.10.2021, 10:16
von Krishty
DerAlbi hat geschrieben: 07.10.2021, 02:07Wie sau. Das ist Hardware-Unterstützt von der CPU. Für den GDB gibt es z.B. "rwatch" für lesend-zugriff und "awatch" für r/w-zugriff. Andere Debugger können das sicher auch.
Geil! Wieder was gelernt.
Man muss nur die vtable-Adresse rausfinden, das sagt einem aber auch der Debugger. Wenn man wirklich ein Log haben will, könnte man die vtable hacken/umleiten und von der injizierten Funktion aus die Originalfunktion aufrufen, nachdem man fein geloggt hat.
Du musst aber die vtables aller abgeleiteten Klassen umleiten. Schließlich geht der Aufruf nicht durch den Basis-vtable.
Man hätte dann sogar direkt den Call-Ursprung irgendwo in den Registern stehen (oder im Stack?) - wie man die Code-Adresse in Datei und Zeilennummer umwandelt.... hmmh.
Falls wir von Windows sprechen, sind das bloß ein paar Zeilen, ja. Das Stack Layout ist ab Windows x64 fest definiert und Backtrace wird vom Kernel angeboten; Umwandlung der Adressen in Funktionsnamen und Zeilennummern von der DbgHelp-API.
Und wie man in read-only-Speicher rumschreibt... weiß hier sicher jemand, stimmts, Krishty?
Wäre einfacher, das readonly-Segment im Linker temporär auf R/W umzuleiten :P

Re: CPP, Zugriff bzw. überladen von "->" Member Access Operators

Verfasst: 07.10.2021, 15:46
von Lord Delvin
Ist die vtable nicht immer bei *this?
Dürfte aber nicht für Mehrfachvererbung gehen.

Kann dir das die IDE nicht einfach zeigen? Oder hast du kein Projekt mit einer IDE, die die callees einer Funktion findet? Oder sind das einfach zu viele uninteressante?
Ich habe öfter mal so Probleme, wo man zwar 50-60 Callees anschauen muss, es aber trotzdem schneller ist, als live zu schauen, weil du bei letzterem das Problem hast, dass du ja irgendwie sicherstellen musst, dass du dabei an allen Callees vorbei gekommen bist.

Re: CPP, Zugriff bzw. überladen von "->" Member Access Operators

Verfasst: 08.10.2021, 08:29
von joggel
@injizierte Funktion, vtable hacken und co.
Mal ne blöde Frage.
Ich habe bestimmt nicht alles verstanden, aber bezüglich dieser Aussage:
Man muss nur die vtable-Adresse rausfinden, das sagt einem aber auch der Debugger. Wenn man wirklich ein Log haben will, könnte man die vtable hacken/umleiten und von der injizierten Funktion aus die Originalfunktion aufrufen, nachdem man fein geloggt hat.
Ich denke hier ist debugging gemeint..
Würde sowas auch funktionieren, wenn ich sowas im meinem Programm selbst implementiere?
Also, ich habe eine Instanz einer Klasse und will in einer bestimmten Funktion ein Logging haben ohne den SourceCode der Klasse anzufassen. Also...quasi hacken. So könnte man ja zB prima Logger verwenden können.

Ich rede jetzt aber nicht von einer separaten EXE, sondern sozusagen eine extra Funktionalität im selben C++-Projekt
Also... ich hoffe, es konnte verstanden werden was ich meine?

Würde das irgendwie funktionieren? Gibt es evtl dazu irgendwelche Links?

Re: CPP, Zugriff bzw. überladen von "->" Member Access Operators

Verfasst: 08.10.2021, 08:44
von Krishty
Lord Delvin hat geschrieben: 07.10.2021, 15:46Ist die vtable nicht immer bei *this?
Dürfte aber nicht für Mehrfachvererbung gehen.
Nein, bei this sitzt nur ein Zeiger auf die vtable. a) hinge sonst die Konstruktionsgeschwindigkeit einer Instanz von der Anzahl ihrer virtuellen Funktionen ab; b) möchte man die vtables möglichst in read-only-Speicher sehen, weil C++ keinen Use Case hat, in dem man sie zur Laufzeit verändern können sollte.

Re: CPP, Zugriff bzw. überladen von "->" Member Access Operators

Verfasst: 08.10.2021, 08:48
von Krishty
joggel hat geschrieben: 08.10.2021, 08:29Würde das irgendwie funktionieren? Gibt es evtl dazu irgendwelche Links?
Ja; beim Cheaten in Spielen dürfte das regelmäßig gemacht werden.

Hier gibt's Beispiel-Code; habe aber nicht seine Qualität bewertet: https://stackoverflow.com/questions/314 ... t-function

Re: CPP, Zugriff bzw. überladen von "->" Member Access Operators

Verfasst: 08.10.2021, 08:58
von joggel
Danke Dir :)

Re: CPP, Zugriff bzw. überladen von "->" Member Access Operators

Verfasst: 08.10.2021, 09:48
von Alexander Kornrumpf
joggel hat geschrieben: 08.10.2021, 08:29 @injizierte Funktion, vtable hacken und co.
Mal ne blöde Frage.
Ich habe bestimmt nicht alles verstanden, aber bezüglich dieser Aussage:
Man muss nur die vtable-Adresse rausfinden, das sagt einem aber auch der Debugger. Wenn man wirklich ein Log haben will, könnte man die vtable hacken/umleiten und von der injizierten Funktion aus die Originalfunktion aufrufen, nachdem man fein geloggt hat.
Ich denke hier ist debugging gemeint..
Würde sowas auch funktionieren, wenn ich sowas im meinem Programm selbst implementiere?
Also, ich habe eine Instanz einer Klasse und will in einer bestimmten Funktion ein Logging haben ohne den SourceCode der Klasse anzufassen. Also...quasi hacken. So könnte man ja zB prima Logger verwenden können.

Ich rede jetzt aber nicht von einer separaten EXE, sondern sozusagen eine extra Funktionalität im selben C++-Projekt
Also... ich hoffe, es konnte verstanden werden was ich meine?

Würde das irgendwie funktionieren? Gibt es evtl dazu irgendwelche Links?
Die Sache ist ja die, wenn du diese Funktionalität per Design haben willst und nicht im Nachhinein als Workaround in Legacy, dann würdest du es nicht über die vtable machen, sondern mit irgendwas was die Sprache tatsächlich unterstützt.

Eine Möglichkeit das in C++ zu realisieren, die ziemlich nahe an der ersten Idee von Walker ist, wäre einen smart-pointer anzubieten, der loggen kann, denke ich.

Re: CPP, Zugriff bzw. überladen von "->" Member Access Operators

Verfasst: 08.10.2021, 10:54
von joggel
Ja, ich weiß.
Ich fragte auch nur eher interessehalber :)

Re: CPP, Zugriff bzw. überladen von "->" Member Access Operators

Verfasst: 08.10.2021, 12:12
von DerAlbi
Also das vtable-hacking ist von meiner Seite aus schon so gedacht, dass man das selbst implementiert.
1) Du lässt dir einfach die Adresse einer virtuellen Funktion geben
2) Du musst den Funktionspointer selbst devirtualisieren (Member-Function-Pointer auf virtuelle funktionen sind irgendwie nicht die Funktion selbst und irgendwie auch doppelt so groß wie ein normaler Pointer - dafür gibts aber Lösungen, die man googeln kann)
3) Du suchst nach dem Pointer in den vtables und ersetzt ihn durch eine loggende Funktion mit gleicher Signatur und Aufrufkonvention (this-call?)
4) Du loggst und reichst die Parameter an die Originalfunktion weiter.

Das ist eine Code-Lösung, die im nachhinein in gewissen grenzen geht. Scheitern tut man, wenn aufgrund von Optimierung der Compiler gewisse Calls direkt devirtualisiert, aber wenn man die Optimierung runterschraubt, sollte das klappen.

Ich kann nicht sagen, dass das alles sehr einfach ist. Das ist weder Portabel noch elegant - viel mehr "interessant". Aber wenn es einmal läuft, ist es für den Zweck sicher ok. Man muss halt am Anfang viel mit dem Debugger arbeiten und einige Crashs in kauf nehmen.. aber ich denke, dass man dabei eine ganze Menge lernt.

Re: CPP, Zugriff bzw. überladen von "->" Member Access Operators

Verfasst: 08.10.2021, 13:41
von joggel
Wäre definitiv mal interessant das auszuprobieren...

Re: CPP, Zugriff bzw. überladen von "->" Member Access Operators

Verfasst: 08.10.2021, 17:41
von Lord Delvin
Krishty hat geschrieben: 08.10.2021, 08:44
Lord Delvin hat geschrieben: 07.10.2021, 15:46Ist die vtable nicht immer bei *this?
Dürfte aber nicht für Mehrfachvererbung gehen.
Nein, bei this sitzt nur ein Zeiger auf die vtable. a) hinge sonst die Konstruktionsgeschwindigkeit einer Instanz von der Anzahl ihrer virtuellen Funktionen ab; b) möchte man die vtables möglichst in read-only-Speicher sehen, weil C++ keinen Use Case hat, in dem man sie zur Laufzeit verändern können sollte.
So meinte ich das nicht.
Eher bei Offset 0 des real repräsentierenden Structs, d.h. mit load(this) bekommst du den VTable Pointer.
Also zumindest die für die aktuelle Sicht auf das Objekt, oder ist Mehrfachvererbung nicht mehr mit this-Pointer-Adjustment?

Re: CPP, Zugriff bzw. überladen von "->" Member Access Operators

Verfasst: 08.10.2021, 17:45
von Krishty
Du hast recht; ich habe den Deref-Operator übersehen! Ja; Mehrfachvererbung verändert noch immer this. AFAIK werden dafür extra Thunks erzeugt. Die vftable zeigt dann also noch nicht einmal auf die finale Funktion, sondern nur auf den Thunk, der this verändert und dann auf die Zielfunktion weiterleitet. In dem Fall funktioniert das Überschreiben der vtable also nur mit großem Aufwand …

Re: CPP, Zugriff bzw. überladen von "->" Member Access Operators

Verfasst: 09.10.2021, 13:26
von DerAlbi
Neeeeein, es ist nicht soooohh kompliziert. Du musst eh händisch devirtualisieren und dafür gibt es pro Compiler Google-Code.
Beim Devirtualisieren musst wird man eh aus der vtable lesen und wenn man sich den Code anschaut, kann man in dem Moment, wo man den finalen Pointer liest, auch einfach direkt einen anderen finalen Pointer hinschreiben.
Das Devirtualisieren ist vermutlich auch recht einfach reverse-enigneerbar via godbolt, indem man mit minimaler Optimierung einen virtuellen Member-Function-Pointer-Call auslöst. Das wird nicht mehr als ein paar zeilen ASM generieren die man in C++ hinschreiben muss.
Hat man einmal den finalen Pointer, kann man einfach alle vtables (das ganze Datensegment dafür) durchsuchen und entsprechend alle Matches umleiten.