Immer Die Größe einer C++ - Funktion
Las ich diese Frage, weil ich versuche zu finden, die Größe einer Funktion in einem C++ - Programm, Es wird angedeutet, dass es vielleicht ein Weg, dass ist Plattform-spezifisch. Meine Zielplattform ist windows
Die Methode, die ich derzeit noch in meinem Kopf ist folgende:
1. Sie erhalten einen Zeiger auf die Funktion
2. Erhöhen Sie den Zeiger (& counter), bis ich erreicht die Maschine code-Wert für ret
3. Der Zähler wird die Größe der Funktion?
Edit1: um Zu verdeutlichen, was ich damit meine "Größe" meine ich, dass die Anzahl der bytes (Maschinen-code), aus denen sich die Funktion.
Edit2: Es wurden ein paar Kommentare und Fragen, warum oder was ich Plane zu tun. Die ehrliche Antwort ist, ich habe keine Absicht, und ich kann nicht wirklich sehen, den nutzen von wissen ein-Funktionen Länge der pre-compile-Zeit. (obwohl ich bin sicher, es gibt einige)
Scheint das eine gültige Methode, um mich, werden Sie diese Arbeit?
- Und Sie wollen dies tun, weil...?
- Unsicher, aber wenn eine Funktion mehrere gibt. Bedeutet das nicht, dass Sie immer fehlerhafte Ergebnisse ?
- Was ist, wenn es mehr als eine
ret
? Was ist, wenn die Funktion inlined? Was ist, wenn die Funktion sub-Funktionen eingebettet in es? - Schlechte Idee. Was genau wollen Sie archivieren?
- Durch die Art und Weise, C++ ist nicht besser als C auf diese Weise, wenn nicht noch schlimmer.
- diese Frage ist so seltsam, dass es sein muss, für hack/exploit-code. Aus dieser Sicht, die ich weigere mich zu unterhalten, diese Frage.
- Irgendwo, ich kann Ihnen versichern, es ist für pädagogische Zwecke nur.
- Es ist traurig zu sehen, so viele negative Antworten auf eine Frage, die man einfach hätte in der Verfolgung von hart-zu-erhalten Kenntnisse über computer-Architektur. Wir sollten die OP den Vorteil des Zweifels.
- Es gab eine Menge von Kommentaren/Antworten mit hinweisen auf Fallstricke, aber nur wenige (nur eine oder zwei, soweit ich das sagen kann), die nur die bash die Frage. Ich habe eigentlich erwartet, dass es um downvotes und/oder in der Nähe Stimmen, aber soweit ich sehen kann keiner von denen.
- zum Zeitpunkt meines Kommentars waren es drei von sechs nicht-OP-Kommentare, die sagen das ist eine schlechte Idee, böswillig oder sinnlos. Nicht zu verstehen, den Punkt von etwas und die Frage ist üblich und sinnvoll SO, aber der Ton der diese waren definitiv ablehnend.
- fsmc ist richtig, die Mehrheit der frühen Kommentare waren auf jeden Fall in dieser form, und ich habe den gleichen Ton von Ihnen als gut. Aber ich habe einige nützliche Antworten (Speziell Micheal Madsen & dsmc ist), ich denke, ich werde einen Blick in einige Demontage Bibliotheken und sehen, ob ich finden kann, eine verlässliche Methode. 😉
- Mögliche Duplikate von: stackoverflow.com/questions/3569877/...
- Adam: Haben einige dieser Kommentare gelöscht wurden? Für das Protokoll, ich glaube nicht, dass die Kommentare, die angibt, dass das eine schlechte Idee oder Fragen, warum Sie wollen, dies zu tun, sind negativ. Oft fragt jemand eine Frage zu einem bestimmten Ansatz zur Lösung eines Problems, wenn das, was Sie wirklich Fragen sollten, ist, was Vorgehen sollte. Manchmal kann es schwierig sein zu bestimmen, von einer Frage, ob es die 'Akademische', oder wenn es ist wirklich über einige unausgesprochene Ziel. Zugegeben, einige der Kommentare, die vielleicht knapp, aber das bedeutet nicht, dass Sie dazu bestimmt waren, negativ zu sein. Sicherlich nicht beleidigend, richtig?
- dass etwas eine schlechte Idee, UND nicht verstehen, den Punkt oder Zweck (impliziert durch die Fragen "warum tun dies" nach dem Aufruf eine schlechte Idee), gleichzeitig ist darauf hindeutet, dass es sinnlos ist. Der Sinn eines solchen Kommentars ist eindeutig nicht zu ermitteln, ob Akademische oder nicht. Unter der Annahme, dass etwas neugierig, muss bösartig sein ist auch nicht so schön. Es gibt viele Wege zu Fragen, warum jemand wollen, um etwas zu tun. Ein curt Beispiel wäre "was machst du das?", aber die Fragen hier sind mehr und schlage vor, die Frage nicht sinnvoll.
Du musst angemeldet sein, um einen Kommentar abzugeben.
Nein, das wird nicht funktionieren:
ret
Unterricht.ret
können Sie nicht nur Blick auf die einzelnen bytes - weil der entsprechende Wert erscheinen könnte, als einfach nur ein Wert, sondern als eine Anleitung.Das erste problem kann möglicherweise umgangen werden, wenn Sie beschränken Sie Ihre coding-Stil zu, sagen wir, nur einen einzelnen Punkt der Rückkehr in Ihre Funktion, aber die anderen erfordert grundsätzlich ein disassembler also man kann sagen, die einzelnen Anweisungen voneinander entfernt.
ret
s oder die Möglichkeit eines arg mit den entsprechenden opcode-Wertret
Linie. GCC ist manchmal Optimierungen, wo es springt einfach in eine andere Funktion/block enthält dieret
schon. Ich nehme an, wenn es beendet nur mit Ausnahmen es möglicherweise nicht über eine Rückkehr entweder.jmp Cont; db 01; Cont: ...
ist, legen Sie die byte -0x01
zwischen denjmp
- Anweisung und der next-Anweisung. Wenn Sie die Schritte durch, es gibt keine Möglichkeit für Sie zu wissen, dass die0x01
nicht der start des nächsten Befehls, also alles zerlegt, nach diesem Punkt wird sein Müll. Dies ist der Grund, warum es so schwer ist zu schreiben, ein disassembler für x86.Es ist möglich, um alle Blöcke der Funktion, sondern ist ein unnatürlicher Frage, was ist die 'Größe' einer Funktion. Optimierte code ordnen code-Blöcke in der Reihenfolge der Ausführung und bewegt sich nur selten genutzt Blöcke (Ausnahme Pfade) in die äußeren Teile des Moduls. Für mehr details, siehe Profil-Gesteuerte Optimierungen zum Beispiel, wie Sie mit Visual C++ erreicht dies in der link-time code generation. So eine Funktion kann beginnen bei Adresse 0 x 00001000, Niederlassung 0x00001100 in einem Sprung auf 0x20001000 und ein ret, und haben einige exception-handling-code 0x20001000. Bei 0x00001110 eine andere Funktion gestartet wird. Was ist die 'Größe' Ihrer Funktion? Es tut Spanne von 0 x 00001000 bis +0x20001000, aber es 'besitzt' nur paar Blöcke, die span. Also deine Frage sollte sein, ungefragt.
Gibt es andere, berechtigte Fragen in diesem Zusammenhang, wie die Gesamtzahl der Anweisungen, die eine Funktion hat (kann bestimmt werden aus der Programm-symbol-Datenbank und aus dem Bild), und noch wichtiger, was ist die Anzahl der Instruktionen in der Häufig ausgeführten code-Pfad in der Funktion. All dies sind Fragen, die eigentlich gestellt werden, im Kontext des performance measurement und gibt es tools, die instrument-code und kann damit sehr ausführlichen Antworten.
Jagen Zeiger im Speicher und auf der Suche nach
ret
bekommst du nirgendwo, fürchte ich. Moderne-code ist Weg, Weg, Weg, viel komplexer als das.uf module!functioname
.uf
hat einen ziemlich guten job bei voller Funktion der Demontage, selbst wenn der Umgang mit der code-Fetzen in Stücke von einer modernen generation/Optimierungs-tool, und ich kann sehen, dass die Anzahl der Anweisungen, Blöcke usw.Wow, ich verwenden Sie die Funktion Größe zählen alle die Zeit und es hat viele Verwendungen. Ist es zuverlässig? Kein Weg ist. Ist es standard c++? Kein Weg ist. Aber das ist, warum Sie überprüfen müssen, um es in den disassembler, um sicherzustellen, dass er funktioniert, dass man jedes mal eine neue version. Compiler-flags können mess up die Bestellung.
Es scheint zu funktionieren besser mit statischen Funktionen. Globale Optimierungen können es töten.
P. S. ich hasse Leute, die Fragen, warum Sie dies tun wollen, und es ist unmöglich, etc. Stoppen Sie diese Fragen bitte. Macht Sie blöd klingen. Programmierer sind oft gefragt, um nicht-standard-Sachen, weil die neuen Produkte fast immer die Grenzen dessen, was ist verfügbar. Wenn Sie nicht, Ihr Produkt ist wohl eine Neuauflage dessen, was bereits getan worden. Langweilig!!!
0xCCCCCC
direkt auf die main-Funktion. Ich hatte eine manuelle Bereinigung des stack via ASM direkt.__asm MOV ESP, EBP __asm POP EBP __asm RETN
Zudem die genaue Länge der Funktion, ohne die Platzhalter, die ich geändert ++Länge Länge++Diese wird nicht funktionieren... was wenn es ein Sprung, ein dummy
ret
, und dann das Sprungziel? Ihr code wird getäuscht werden.Im Allgemeinen, es ist unmöglich dies mit 100% Genauigkeit, denn Sie müssen Vorhersagen, alle code-Pfade, die ist wie die Lösung der Halteproblem. Erhalten Sie "ziemlich gut" Genauigkeit, wenn Sie bei der Umsetzung Ihrer eigenen disassembler, aber keine Lösung ist fast so einfach wie du dir das vorstellst.
Einen "trick" wäre es, herauszufinden, welche Funktion der code ist nach die Funktion, die du suchst, das wäre ziemlich gute Ergebnisse unter der Annahme bestimmter (gefährlicher) Annahmen. Aber dann müsstest du wissen, welche Funktion kommt nach Ihrer Funktion, die, nach Optimierungen, ist ziemlich schwer herauszufinden.
Edit 1:
Was ist, wenn die Funktion nicht, am Ende sogar mit einem
ret
Unterricht an alle? Es könnte sehr gut nurjmp
an den Aufrufer zurück (obwohl es unwahrscheinlich ist).Edit 2:
Vergessen Sie nicht, dass x86, zumindest hat die variable-Länge-Anweisungen...
Update:
Für diejenigen, die sagen, dass flow-Analyse ist nicht das gleiche wie die Lösung das Halteproblem:
Überlegen, was passiert, wenn Sie code haben wie:
Du wird Folgen die springen jedes mal, um herauszufinden, die am Ende der Funktion, und Sie nicht ignorieren, es Vergangenheit das erste mal, weil Sie nicht wissen, ob oder nicht Sie sind den Umgang mit self-modifying code. (Sie können die inline-assembly in den C++ - code, der ändert sich selbst, zum Beispiel.) Es könnte sehr gut erweitern auf eine andere Stelle des Speichers, so dass Ihr Analysator wird (oder sollte) Ende in einer Endlosschleife, es sei denn, Sie tolerieren false negatives.
Ist das nicht wie das Halteproblem?
ret
. Wir haben den start, so ist es durchaus möglich, alle aufzuzählen, die möglichen Pfade, die inret
s. Dann könnte man entfernen Teile dieser Pfade sind die gleichen, und die Summe der verbleibenden Pfad byte-Längen. Es dauert möglicherweise exponentieller Zeit (in extrem seltenen Fällen), aber es ist nicht incomputeable. (Ich Stimme mit Ihnen überein, dass die OP sollte nicht versuchen, dies zu tun, aber unmöglich ist ein starkes Wort.jmp
statt einerret
obwohl? Niemand sagte, es hat ein Ende mit einemret
... es springen konnte, um ein register-Wert, möglicherweise die Anrufer, oder vielleicht um den code unmittelbar nach sich. Wo kommt also Ihre Funktion Ende genau?jmp
zurück. Zwar mag es scheinen, eine sinnvolle Sache, weil der Unterschied in der Semantik zwischen den beiden Anweisungen, es ist generell eine schlechte Idee auf x86 weil es oft bricht der Zweig predictor.__declspec(naked)
- und inline-Montage (oder den Gegenwert in Ihrem compiler).jmp
stattret
, und lassen Sie die virtuelle Methode einen Rückgabe statt? Wenn das "Ende" von Ihrer Funktion in diesem Fall?ret
dann ja, das problem wird schwer. Ich würde immer noch behaupten nicht unmöglich, aber ich kann nicht mit einem Algorithmus aus der Spitze von meinem Kopf. In Antwort auf Ihre bisschen auf deine Antwort, meine Antwort ist zu sehen, wie die zyklomatische Komplexität berechnet wird -- da der Algorithmus zu tun, die zyklomatische Komplexität ist das gleiche, was ich oben beschreiben. Ihre Schleife Beispiel behandelt wird, indem nicht einem bestimmten Pfad eine Schlaufe bilden.ret
, dann kann man nie wissen, Wann wirst du erreichen, wenn Sie nach jeder Schleife...jmp
zu, und keine Ahnung, wo Sie weiterhin auf der Suche nach einemret
.jmp
Problem, wenn dein compiler optimiert sich dieret
s von dem Anrufer.ret
, noch bin ich sagen, dass die OP wird in der Lage sein, so etwas umzusetzen. Ich bin zu sagen, dass dies nicht ein nachweislich incomputable problem. (Wenn Sie produzieren können, ein Beweis dafür, dass ich glücklich sein würde, stand korrigiert, aber zu sagen "ich denke, es ist wie das Halteproblem" oder "hier sind einige Dinge, die es schwer machen," nicht beweisen, dass es incomputable.)call
, es ist eine indirektejmp
. Soweit ich weiß, das ist nicht eine Funktion aufrufen, jetzt, ist es? (Außerdem: Haben Sie einen Blick auf @BlueRaja Kommentar über die Demontage x86 und wie es sein kann unmöglich?)jmp
mitjnz
; das istjnz Cont; db 01; Cont: ...
Die Frage ist, ist die byte -0x01
den Anfang einer Anweisung, oder ist die Instruktion, die von derjnz
den Anfang von der Anleitung? Vorausgesetzt0x01
ist nicht ein Befehl von selbst (es ist nicht), und vorausgesetzt, nur ein Pfad wird immer genommen werden, ist der einzige Weg, die Antwort zu wissen, ist zu wissen, welcher Pfad genommen werden; (Fortsetzung..)jnz
(die kann alles) = 0. Da die Bestimmung der Ausgabe eines beliebigen Programms entspricht dem Halteproblem gibt es keine Möglichkeit, zuverlässig die Frage beantworten.jnz
springt in der Mitte einer Anweisung, werden Sie zwei völlig unterschiedliche code-Pfade, die mit (potenziell) zwei verschiedeneret
Aussagen. Wie wissen Sie, welche ist die richtigeret
(beachte, dass dies nicht immer der "am weitestenret
")? Der einzige Weg, um wissen ist zu wissen, ob diejnz
springen wird oder nicht, das ist eines unentscheidbaren Problems.Die wirkliche Lösung ist, um zu Graben in Ihre compiler-Dokumentation. Die ARM-Compilers, den wir benutzen werden können, um zu produzieren, eine assembly dump (code.dis), aus denen es ziemlich trivial zu subtrahieren des offsets zwischen einem entstellten-label-Funktion und die nächsten entstellt-label-Funktion.
Ich bin mir nicht sicher, welche Werkzeuge benötigen Sie für diese mit einem windows-Ziel jedoch. Es sieht aus wie die Extras aufgelistet die in der Antwort auf diese Frage könnte das sein, was du bist suchen für.
Beachten Sie auch, dass ich (arbeite im embedded-Bereich) davon ausgegangen, dass Sie redeten über post-compile-Analyse. Es noch möglich sein könnte, zu prüfen, diese intermediate-Dateien programmgesteuert als Teil eines build-vorausgesetzt, dass:
Beachten Sie, dass ich bin mir nicht ganz sicher, WARUM Sie wollen, um zu wissen, diese Informationen. Ich habe dies in der Vergangenheit gebraucht, um sicher zu sein, dass ich passen einer bestimmten Stück code in einem ganz bestimmten Ort im Speicher. Ich muss zugeben, ich bin neugierig, welchen Zweck dies hätte über eine Allgemeine desktop-OS Ziel.
Diese kann Arbeit in sehr begrenzten Szenarien. Ich benutze es im Rahmen eines code-injection-Dienstprogramm, das ich schrieb. Ich weiß nicht, wo ich die Informationen gefunden, aber ich habe Folgendes (C++ in VS2005):
Und dann in eine andere Funktion, die ich habe:
Haben Sie zum ausschalten einige Optimierungen und erklären die Funktionen als static zu bekommen, diese Arbeit; ich erinnere mich nicht, die Besonderheiten. Ich weiß nicht, ob dies ist eine genaue byte-Anzahl, aber es ist nahe genug. Die Größe ist nur, dass der unmittelbare Funktion; es sind keine anderen Funktionen, die aufgerufen werden können, von dieser Funktion. Abgesehen vom extremen Rand Fälle wie diese, "die Größe einer Funktion" ist sinnlos und nutzlos.
__declspec(noinline)
zu der Methode, Erklärungen zu verhindern inlining in einemrelease
bauen. 2) Es sollte#pragma runtime_checks("", restore)
statton
3) Warnung:, Wenn Sie planen, die Injektion dieser Methode in einen separaten Prozess, das wird Absturz es, wenn IhrInjectionProc
Methode hat alle statisch verbundenen API-Aufrufe.In C++, die gibt es keine Vorstellung von Funktion, Größe. Zusätzlich zu allem anderen erwähnt, Präprozessor-Makros auch machen, für eine unbestimmte Größe. Wenn Sie wollen zählt die Anzahl Unterricht Worten, Sie können das nicht in C++, weil es nicht existiert, bis es kompiliert wurde.
Was meinst du mit "Größe einer Funktion"?
Wenn du meinst eine Funktion Zeiger, als es immer nur 4 bytes für 32-bit-Systeme.
Wenn du meinst die Größe des Codes haben, als Sie sollten einfach zerlegen generierten code und finden Sie den Einstiegspunkt und am nächsten
ret
nennen. Ein Weg, es zu tun, ist zu Lesen, das instruction-pointer-register am Anfang und am Ende der Funktion.Wenn Sie möchten, um herauszufinden, die Anzahl von Anweisungen aufgerufen, in der durchschnittlichen Fall für Ihre Funktion, die Sie verwenden können, Profiler und teilen Sie die Anzahl von pensionierten Anweisungen auf der Anzahl der Anrufe.
Ich denke, es funktioniert auf windows-Programme erstellt, die mit msvc, als für die Zweige der 'ret' scheint immer am Ende (auch wenn es Zweige, die Rückkehr Anfang tut es ein jne zu gehen, am Ende).
Sie brauchen jedoch eine Art disassembler-Bibliothek zur Abbildung der aktuellen opcode-Länge, wie Sie variabler Länge für x86. Wenn Sie dies nicht tun, du wirst laufen in der false-positives.
Ich würde mich nicht Wundern, wenn es Fälle gibt, dies nicht zu fangen.
Gibt es keine Einrichtungen, die in Standard-C++ zu erhalten, die Größe oder Länge einer Funktion.
Siehe meine Antwort hier: Ist es möglich zum laden einer Funktion in eine zugeordnete Speicher und von dort aus ausgeführt?
Im Allgemeinen, zu wissen, die Größe einer Funktion ist die Verwendung in embedded-Systemen beim kopieren von ausführbaren code aus einem nur-lese-Quelle (oder eine langsame Speicher-Gerät, wie z.B. einem seriellen Flash) ins RAM. Desktop und anderen Betriebssystemen load-Funktionen in den Speicher mit anderen Techniken, wie dynamische oder gemeinsam genutzte Bibliotheken.
Nur gesetzt, PAGE_EXECUTE_READWRITE an die Adresse, wo Sie bekam Ihre Funktion. Dann Lesen jedes byte. Wenn du byte "0xCC" es bedeutet, dass das Ende der Funktion ist actual_reading_address - 1.
Ich bin dieses posting, um zwei Dinge zu sagen:
1) die Meisten Antworten, die hier gegeben sind wirklich schlecht und wird leicht brechen. Wenn Sie die C-Funktion einen Zeiger (mit name der Funktion), die in einem
debug
bauen Sie die ausführbare Datei, und möglicherweise auch in anderen Fällen kann es zu einem PunktJMP
shim, die nicht die Funktion haben Körper selbst. Hier ist ein Beispiel. Wenn ich den folgenden Code für die Funktion, die ich unten definiert:den
pfn
ich bekomme (zum Beispiel:0x7FF724241893
) wird zeigen, was nur einJMP
Anleitung:Zusätzlich ein compiler kann nest mehrere von diesen Scheiben, oder Zweig Ihrer Funktion code so, dass es mehrere Epiloge, oder
ret
Anweisungen. Was solls, es kann nicht einmal mit einemret
Unterricht. Dann, es gibt keine Garantie, dass die Funktionen selbst kompiliert und verlinkt werden, in der Reihenfolge, die Sie definieren, Sie in den source-code.Können Sie all das Zeug in Montage Sprache, aber nicht in C oder C++.
2), So dass oben war die schlechte Nachricht. Die gute Nachricht ist, dass die Antwort auf die ursprüngliche Frage ist, ja, es gibt einen Weg, (oder ein hack), um die genaue Funktion der Größe, aber es kommt mit den folgenden Einschränkungen:
Es funktioniert in der 64-bit-Programme nur für Windows.
Es offensichtlich ist Microsoft-spezifisch und nicht portierbar.
Muss man dies bei der Laufzeit.
Das Konzept ist einfach-nutzen Sie die Möglichkeit SEH umgesetzt wird in der x64-Windows-Binärdateien. Compiler fügt die details zu jeder Funktion in der PE32+ header (in der
IMAGE_DIRECTORY_ENTRY_EXCEPTION
Verzeichnis der optional header), die Sie verwenden können, erhalten Sie die genaue Funktion der Größe. (Falls Sie sich Wundern, diese information wird verwendet für den Fang, die Handhabung und abwickeln von Ausnahmen in der__try/__except/__finally
Blöcke.)Hier ein kurzes Beispiel:
dann:
GCC verwenden, gar nicht so schwer.
&&do_something_END
verfügbar im Datei-Bereich, so dass es mehr oder weniger nutzlos.folgenden code der die genaue Funktion der Blockgröße, es funktioniert gut mit meinem test
runtime_checks deaktivieren _RTC_CheckEsp im debug-Modus
Die nicht-portable, aber API-basierte und korrekt funktionierenden Ansatz ist die Verwendung von Programm-Datenbank - Reader- wie dbghelp.dll auf Windows oder auf Linux-readelf. Die Nutzung dieser ist nur möglich, wenn debug-Informationen aktiviert/vorhanden zusammen mit dem Programm. Hier ist ein Beispiel, wie es funktioniert auf Windows:
Erhalten Sie die Größe der Funktion in symbol.Größe, aber Sie müssen möglicherweise auch zusätzliche Logik zu identifizieren, ob die angegebene Adresse ist eigentlich eine Funktion, ein shim platziert es durch inkrementelle linker oder ein DLL-Aufruf thunk (gleiche Sache).
Ich denke, etwas ähnliches kann über readelf auf Linux, aber vielleicht haben Sie zu kommen mit der Bibliothek, auf der Oberseite der sourcecode...
Müssen Sie Bedenken, dass, obwohl die Demontage-basierten Ansatz möglich ist, Sie grundsätzlich zu analysieren, gerichteter graph mit Endpunkten in ret, halt, jmp (VORAUSGESETZT, Sie haben inkrementellen Verknüpfung aktiviert, und Sie sind in der Lage zu Lesen jmp-Tabelle, um festzustellen, ob der jmp-Sie sind mit in der Funktion ist die interne Funktion (fehlt im Bild die jmp-Tabelle) oder externe (in diesem Tisch; eine solche jmps Häufig auftreten, als Teil der tail-call-Optimierung auf x64, ich weiß)), alle Anrufe, die gemeint sind, zu nonret (wie eine Ausnahme generieren Helfer), etc.
Es ist eine alte Frage, aber trotzdem...
Für Windows x64, alle Funktionen haben eine Funktion Tabelle, die enthält den offset und die Größe der Funktion. https://docs.microsoft.com/en-us/windows/win32/debug/pe-format . Diese Funktion Tabelle wird verwendet, um zu entspannen, wenn eine Ausnahme geworfen wird.
Sagte, dass dies nicht die Informationen enthalten, wie inlining, und alle anderen Themen, die die Menschen schon angemerkt...