Wie funktioniert die Bildschirmausgabe?

3 Antworten

Vom Beitragsersteller als hilfreich ausgezeichnet

Ursächlich als man reine Videoadapter hatte (Standard VGA, dann SVGA) mußtest Du tatsächlich alle Primitive wie Linien etc. durch setzen einzelner Pixel im Speicher der Karte manipulieren. Grundlegend geht das auch heute noch, sofern die Karte die entsprechenden Schnittstellen bereitstellt.

Wie ein Pixel im Speicher abgelegt wird hängt vom Modus und der Farbtiefe ab. So wurde Highcolor als 16-Bit kodiert und Truecolor als 32-Bit mit einem Leerbyte, einfach weil es schneller geht. So konntest Du in einem Offscreen (Framebuffer) alle Pixel berechnen und dann eben als 32-Bit Worte schnell in den Grafikkartenspeicher kopieren.

Dann kamen 2D-Accelerator, die Dir Primitive boten und schon war die Fensterdarstellung etc. deutlich schneller, wenn man die Arbeit der Grafikkarte übergab. Dann noch 3D usw. usf. .

Da aber jeder Grafikchip seine eigene Schnittstelle hat, hat man das ganze abstrahiert mit GDI(+). Für Games etc. ist das allerdings nicht so pralle gewesen, daher hat man DirectX geschaffen. OpenGL wiederum kommt aus einer ganz anderen Richtung und ist auch strukturell anders aufgebaut. OpenGL konnte man z.B. von Anfang an verteilen.


Usedefault 
Beitragsersteller
 02.08.2020, 22:20

Ist der VideoRAM also nur dafür da, die Pixel des fertigen Bildes zwischen zuspeichern und der Bildschirm schaut pausenlos was da drinnen ist?

Oder muss man einen Call machen und dann Redrawed der Bildschirm den Buffer?

Gibt es deswegen dieses "LockBuffer", damit nicht zwei Prozesse zugleich den VRAM ändern?

KarlRanseierIII  02.08.2020, 22:31
@Usedefault

Jein, bei heutigen Karten hält der VideoRAM nicht mehr nur den (oder die) Framebuffer, aus denen dann die Ansteuerung für den Monitor generiert wird.

Aber grundlegend, bei Röhren wird das Bild mit XX Hz dargestellt, hast Du den Speicher während des Auslesens durch den Videoadapter verändert, dann gab es halt Mischbilder - Tearing gehört zu dieser Klasse an 'Fehldarstellungen'. Im Endeffekt hast Du früher einfach gewartet, bis der Rücksprung anstand, dann hast Du reinkopiert.

Bei Panels ist das alles ohnehin ein wenig anders, in letzter Konsequenz mußt Du Dich nach der Schittstelle richten. Wenn es einen expliziten Redraw gibt wird die Doku sagen, ob er synchronisiert oder wie zu synchronisieren ist.

Locks dienen üblicherweise zur Synchronisation zwischen Threads/Prozessen, wie das mit dem LockBuffer bei WGI? ist kann ich Dir nicht sagen.

Usedefault 
Beitragsersteller
 02.08.2020, 23:18
@KarlRanseierIII

Das heißt der Röhrenbildschirm konnte technisch bedingt nur mit 50Hz oder so redrawed werden?

Dann mussten Game loops wohl nur alle 3 Cycles Drawen weil sonst kommt man ja auf keine 200 fps?

KarlRanseierIII  02.08.2020, 23:41
@Usedefault

Ja, oder eben mit 60Hz, oder 75Hz, oder 120Hz. Das hing vom jeweiligen Monitor ab, ist heute im Endeffekt wegen der Reaktionszeit des Panels auch nicht anders.

Wie meinst Du das mit den 3 Cycles udn 200 fps?

Usedefault 
Beitragsersteller
 02.08.2020, 23:55
@KarlRanseierIII

Weil in einem Tutorial gesagt wurde ein Spiel läuft etwa mit 200 fps, aber das geht ja nicht das Bild nur 60mal redrawed werden kann technisch bedingt?

Aber etwas anderes: Gibt es heute immer noch einen Speicher wo nun die ganzen Pixel drinn stehen? GetPixelColor macht es ja auch irgendwie? Ist das auf der Grafikkarte nun gespeichert?

KarlRanseierIII  03.08.2020, 00:15
@Usedefault

Ja, der 'Videoadapter'-Bereich der Grafikkarte muß ja irgendwie das Signal für den Monitor erzeugen, egal welche Schnitttstelle. Dazu braucht er einen Framebuffer in dem der Zustand, den jeder Punkt am Display haben soll, gespeichert wird.

Die 200fps mit denen das Spiel läuft, ist die Anzahl der Frames, die die GPU rendern kann. Wenn der Frame gerendert ist, wird der FB eben aktualisiert/umgeschaltet und wenn die nächste Aktualisierung ansteht, erscheint die Änderung eben am Display. Wurde der FB eben 2 mal aktualisiert, dann wird das eien Zwischenbild eben nicht merh angezeigt.

Usedefault 
Beitragsersteller
 03.08.2020, 00:21
@KarlRanseierIII

Und kann man den Framebuffer der letztendlich ausgegeben wird einsehen irgendwie oder ändern? Woher bekommt GetPixelColor seine Infos?

KarlRanseierIII  03.08.2020, 00:57
@Usedefault

Ja, natürlich, wenn ich z.B. direkt die HW programmiere und auf den physischen Speicher zugreifen kann. Unter einem OS wie Windows müßte Dir der Framebuffer in den Adressbereich gemapped werden - Aber eigentlich möchte ein OS nicht, daß Du sowas kannst, außer vielleicht, Du bist ein Treiber.

Ich weiß nicht wie Dein GetPixelColor arbeitet und worauf genau es arbeitet, dazu müßte man sehr genau wissen, wie das alles implementiert ist.

Usedefault 
Beitragsersteller
 03.08.2020, 01:51
@KarlRanseierIII

HW?

Das heißt wenn ich mit GDI32 Funktionen den Hintergrund vom Window Blau mache oder einen Text schreibe, dann sendet die GDI.dll das an einen Treiber und der ändert dann den Framebuffer und der Framebuffer wird dann ausgegeben?

Usedefault 
Beitragsersteller
 03.08.2020, 01:55
@Usedefault

Bzw erstellt dann Windows ganz automatisch ein Gesamtbild weil es ja ueber RegisterClass weiß wo alles Windows usw. sind?

KarlRanseierIII  03.08.2020, 03:27
@Usedefault

Ja, im Prinzip berechnet GDI das Gesamtbild, greift auf die Dienste des DDI Treibers zurück, der widerum direkt in den Framebuffer der Grafikkarte schreibt.

Ohne jetzt die gesamte Doku von GDI zu lesen, wird GDI aber irgendwie die gesamte Struktur von allem, was ggf. gezeichnet werden muß, irgendwo aufbewahren. Ob das Rendering direkt in den FB der Grafikkarte geht, oder in einen virtuellen Framebuffer (Offscreen) und dann nur noch kopiert wird, steht ggf. irgendwo in der Doku oder bleibt eines der Implementierungsdetails, die man nicht erfahren wird.

KarlRanseierIII  03.08.2020, 03:32
@KarlRanseierIII

Und in neueren Versionen sieht es anders aus, da geht das an den GDI-Kernel über den canonical display driver zu DXGKRNL.SYS.

Usedefault 
Beitragsersteller
 03.08.2020, 17:56
@KarlRanseierIII

Der Bildschirm sind ja im Prinzip nur LCDs bei mir und der Framebuffer bestimmt wie die Leuchten in dem dort alle Pixel der Reihe nach sind oder?

Angenommen man schreibt etwas in den Framebuffer rein, bleibt das dann solange am Bildschirm bis man den Framebuffer ändert?

Oder muss man laufend Redrawen damit das Bild immer "mitgezeichnet" wird, weniger aktualisiert?

Dann bedeutet "über das alte Bild drüberzeichnen" eigentlich den Framebuffer ändern.

Weil wenn ich jetzt ein Window über dem anderen habe wird ja nicht 2 Buffer overlapped sondern ein Gesamtbild erzeugt oder?

KarlRanseierIII  03.08.2020, 22:41
@Usedefault

Der eigentliche Videospeicher/Framebuffer aus dem die Ansteuerung fürs LCD wird ist soweit statisch. D.h. ändert sich der Inhalt nicht, wird vom entsprechenden Teil der Grafikkarte aus diesen Daten das stets gleiche Bildsignal für den Monitor erzeugt und dieser bleibt auch statisch in seinem Zustand.

Und ja, Änderungen im Framebuffer führen beim nächsten Refresh durch die Grafikkarte in der Signalausgabe zu änderungen, die dann zu Änderungen in den Pixeln am LCD werden.

Zur letzten Frage, genau DAS ist eine der Sachen, die sich im Laufe der Zeit geändert hat. Heute hat jedes Applikationsfenster ein eigenes Surface, also einen virtuellen Framebuffer/Offscreen auf dem es arbeitet. Der Windowsmanager macht dann das Compositing und erstellt daraus das finale Anzeigebild durch entsprechende Aktualisierung des Videospeichers. Teile des Compositings wiederum werden ggf. an die GPU ausgelagert, in diesem Fall aktualisiert die dann den Videospeicher.

Es gibt nicht genazu den einen Weg oder Ablauf, denn die Hardware- udn Softwarelandschaft ist nicht homogen.

Usedefault 
Beitragsersteller
 04.08.2020, 01:12
@KarlRanseierIII

Refreshed sich die Grafikkarte nun von alleine oder durch Aufforderung des OS und vorher ein Refresh(); aus der DirectX API?

Usedefault 
Beitragsersteller
 04.08.2020, 01:45
@KarlRanseierIII

Du hast oben geschrieben Änderung im Grafikbuffer führen beim nächsten Refresh zu Änderungen des Bildes.

Wird das Bild auch aktualisiert, wenn nichts geändert wurde oder muss man dem Bildschirm manuell mitteilen bitte nimm den neuen Framebuffer?

KarlRanseierIII  04.08.2020, 02:39
@Usedefault

Der entsprechende Teil der Grafikkarte liest zyklisch den Puffer und erzeugt daraus das Signal für den Monitor (Die Frequenz auf die sich Monitor und Grafikkarte geeinigt haben, z.B. 60 Hz, legt die Zykluslänge fest). Er interessiert sich für nichts, wenn er Speicherstelle X ausliest, um die Pixelinformationen für die Übertragung aufzubereiten, dann nimmt er eben, was gerade in der Speicherstelle steht.

Usedefault 
Beitragsersteller
 04.08.2020, 03:27
@KarlRanseierIII

Und woher weiß dann die Grafikkarte wenn sie den Framebuffer mit dem Swapbuffer tauschen muss? Die können ja asynchron sein ohne Informationsaustausch wann begonnen wurde?

KarlRanseierIII  04.08.2020, 03:59
@Usedefault

Richtig, dann kommt es eben zu Darstellungsfehlern.

Wenn ich mit der Aktualisierung warte, bis ein Zeilenrücksprung kommt, dann entsteht Tearing, ich bekomem zwei Bereiche aus unterschiedlichen Frames. Synchronisiere ich mit der Abtastlücke zwischen den Bildern, warte also den Rücksprung ab, vermeide ich solche Darstellungsfehler (Das ist die VSYNC).

Die Grafikkarte kennt keine SwapBuffer, die tauscht auch nichts, oder vielleicht doch, das hängt von der Implementierung der Hardware ab, aber auch das sagte ich bereits.

Nehmen wir mal einen einfachen Fall, die Grafikeinheit hat genau einen einzelnen Bildspeicher/Framebuffer, aus dem sie das Signal erzeugt. Ferner hat der Chip Register, die ich auslesen kann und anhand derer ich ermitteln kann, wann der nächste Rücksprung ansteht. Dann kann eine Highlevel-Funktion wie Swapbuffer() eben abwarten, bis die Grafikeinheit den Zustand erreicht, an dem die Aktualisierung (ohne Nebeneffekte) stattfinden kann.

Oder meine Grafikkarte hat eigentlich 2 Buffer, der aus dem das Bild generiert wird und einen der modifiziert wird. Sind alle Modifikationen fertig, signalisiert Swapbuffer() der Grafikeinheit, sie solle beim nächsten Rücksprung eben aus dem anderen Buffer lesen. Beide vertauschen ihre Rollen. Genausogut kann die HW aber auch anstatt eines Swaps ein Move machen, sie muß dann halt nur während des Moves Transfers in den Quellbuffer unterbinden.

Oder, oder, oder.

Wenn du eine spezifische High Level API nutzt, dann mußt Du deren Doku lesen, wie sie die verschiedenen HW-Modelle abstrahiert, welche Aufgaben sie übernimmt und was Du ggf. machen mußt.

Usedefault 
Beitragsersteller
 04.08.2020, 18:00
@KarlRanseierIII

Das heißt der Bildschirm bekommt den Buffer immer von der Grafikkarte (weil das schneller geht?) und SwapBuffer funktioniert dann nur, wenn man 2 Grafikbuffer hat? Und nicht etwa 2 Buffer im RAM von denen immer einer in den Grafikspeicher übertragen wird?

Framebuffer ist dann immer genau der Buffer der letztendliche gedrawed wird und wenn es 2 Grafikbuffer gibt wird der zweite dann zum Framebuffer während der amdere wieder bearbeitet wird?

KarlRanseierIII  04.08.2020, 23:05
@Usedefault

Framebuffer ist ein generischer Begriff.

Der Bildschirm bekommt die Daten der Pixel als Signal ermittelt, das Signal muß erzeugt werden und währenddessen muß der entsprechende Teil der Grafikkarte darauf zugreifen können. Das kann je nach Entwurf auch im normalen RAM liegen.

Ich habe jetzt schon mehrfach darauf verwiesen, daß die Details von der konkreten Implementierung der Hardware abhängen.

Und eine High Level Funktion wie Swapbuffer kann auch mit 2 FBs im RAM arbeiten, wobei der aktive einmalig nach dem Wechsel in den 'Ausgabebuffer' geblittet wird.

Grundlegend kannst Du auch so entwerfen, daß die Grafikkarte sich den Puffer synchronisiert aus dem RAM holt, dann würde Swapbuffer einfach die Quelladresse ändern.

Usedefault 
Beitragsersteller
 05.08.2020, 04:13
@KarlRanseierIII

Geht das mit dem Swapbuffer eigentlich nur im Fullscreen? Weil man drawed ja nur ein Window und was ist mit dem Rest den Windows zeichnet? Oder ist Direct X da so weit alles zu redrawen, wenn man bloß ein Window ändert?

Ein andere Frage falls du sowas weißt: Ich hab bei ner simplen Windowclass nirgend GDI oder SetWindowPos oder was ich schon ergoogelt habe, aber trotz Backgroundcolour = NULL; in der Klasse ist der Windowhintergrund weiß aber nicht so "durchsichtig" und einfach was dort im Grafikspeicher grade ist? Ist das neu?

KarlRanseierIII  05.08.2020, 11:58
@Usedefault

Wie ich sagte: Heute bekommt jeds Fenster ein Surface (einen virtuellen Framebuffer), der Windowmanager macht das Compositing, d.h. nicht sicvhtbare Teile werden geclipped etc. und so das eigentliche Gesamtbild konstruiert. Dann ab damit in den 'Ausgabebuffer'.

Ich sagte jetzt der Windowsmanager macht das Compositing, korrekter wäre zu sagen, er steuert es, denn eigentlich lässt man (sofern möglich) die GPU aus den Surfaces den Rest berechnen. Zu der Berechnung gehört eben auch, welche Teile des Ausgabebildes unverändert bleiben können.

Bezüglich des Hintergrundes muß ich 'raten', aber:

Es gibt ja ein Standarddesign/-theme. Wird also kein Hintergrund festgelegt, sollte die entsprechende Schittstelle (GDI) doch eigentlich dafür sorgen, daß dieser Standardwert verwendet wird und bei der Instanziierung der Fensterhintergrund dann damit gefüllt werden. Für Transparenz wirst Du in der Regel den Alphakanal verwenden und müßtest wohl RGBA-Werte angeben - In wieweit GDI das macht, oder ob da der Alphakanal immer mit Standardwert befüllt wird, weiß ich nicht.

Usedefault 
Beitragsersteller
 05.08.2020, 17:22
@KarlRanseierIII

Das heißt wenn man bei einem Window SwapChain verwendet swapt Windows ganz einfach mit wenn es den Desktop swappt?

Virtuelle Buffer im RAM oder auf der Grafikkarte?

Naja es hat mich halt gewundert weil ich habe das Tutorial geschaut bei 17:00 https://m.youtube.com/watch?v=nQTiSLiNyk4 und bei mir ist weißer Hintergrund obwohl ich auch NULL hab und andere hatten das "Problem" auch nach Google Recherche. Ich will nur nicht dass ständig Windows den Draw macht und ich zeichne dann mit DirectX drüber.

KarlRanseierIII  05.08.2020, 22:50
@Usedefault
Virtuelle Buffer im RAM oder auf der Grafikkarte?

Bei modernen GPUs wird man die GPU das machen lassen und sie werden voraussichtlich irgendwo in deren Speicher liegen.

Du darfst etwas ganz grundlegendes nicht vergessen, der Speicher der Grafikkarte ist Teil des Adressraumes der CPU. Andererseits kann die DMA-Engine der Grafikkarte auch einfach in den normalen RAM schreiben und daraus lesen.

Deswegen gibt es halt verschiedene Möglichkeiten, wie man Dinge umsetzen kann.

https://en.wikipedia.org/wiki/Swap_chain

Es gibt ka prinzipiell auch Grafikeinehiten, die massiv auf normalen RAM zurückgreifen. Unterm Strich braucht Dich das aber alles nicht zu jucken, die High Level API gibt doch im Endeffekt die Funktionen vor udn DU nutzt sie dann einfach.

-------

Ich habe gerade mal in die Doku geschaut, wenn die BackgroundBrush NULL ist, dann muß die App eigentlich den Hintergrund des Fensters zeichnen - so zumindest die Doku.

Usedefault 
Beitragsersteller
 05.08.2020, 23:02
@KarlRanseierIII

Also ist die CPU einfach über einen BUS mit der Grafikkarte verbunden und kann wenn man es befiehlt in deren Speicher reinschreiben?

Und die Grafikkarte ist mit dem RAM verbunden und kann da rumschreiben wie die CPU?

Schau:

https://pastebin.com/cBnMEv6S

Ich habe die while (1) gemacht zum Test halber musst mit Taskmgr beenden so, aber es zeichnet mit weißem Background wieso?

Usedefault 
Beitragsersteller
 06.08.2020, 00:16
@KarlRanseierIII

Das einzige was die Bilder sagen ist dass ich mir mal 10 Stunden YouTube Tutorials dazu ansehen werde!

Weißt du vielleicht zufaellig warum GetTickCount() bei mir 142 Tage liefert, obwohl ich PC erste seit ca. 2 Stunden anhab?

Usedefault 
Beitragsersteller
 06.08.2020, 00:29
@KarlRanseierIII

Gerade rausgefunden: Weil "moderne" PCs anscheinend bei Herunterfahren keinen "richtigen" Shutdown machen sondern nur einen halbherzigen der nicht als Systemstart gilt. Jetzt sind es korrekte 5 min nach "shutdown /r"

KarlRanseierIII  06.08.2020, 00:31
@Usedefault

Du ahnst es nicht. Ich meine bei Suspend (RAM/Disk) wird offensichtlich kein Systemstart durchgeführt. Bei einem Reboot würde ich aber schon einen Neustart erwarten.

Usedefault 
Beitragsersteller
 06.08.2020, 00:34
@KarlRanseierIII

Keine Ahnung ob das besser oder schlechter ist auf alle Fälle verstehe ich jetzt die hohe Zahl bei GetTickCount().

KarlRanseierIII  06.08.2020, 00:35
@Usedefault

Allerdings wiederspricht das immernoch der Doku, eigentlich ist bei 49.7 Tagen schluß, es sei denn Du nimmst GetTickCount64().

Usedefault 
Beitragsersteller
 06.08.2020, 00:41
@KarlRanseierIII

Vielleicht nimmt der Compiler da irgendwie von alleine GetTickCount64().

Mir ist grad aufgefallen, dass ein Window dass paar Sekunden keine GetMessage aufruft als "Zombie" eingestuft wird obwohl man eigentlich seine private while Loop noch haben könnte.

Weil ich hab mich immer gefragt was passiert wenn der MessageQueue zu lang wird, wie ein Postkasten wo keiner die Briefe abholt. Aber ganz zufrieden bin ich immer noch nicht nicht mit der Annahme dass der Queue nicht zu groß werden kann, weil Windows ein Fenster schließt, dass keine Messages abholt.

KarlRanseierIII  06.08.2020, 00:58
@Usedefault

Das ist aber relativ normal bei GUIs - Wenn ein Prozess nicht mehr auf bestimmte Dinge reagiert, unter anderem seine Signalisierungen länger ignoriert, dann ist er 'frozen'.

Bei Linux hat Zombie allerdings als Prozesszustand eine andere Bedeutung - Das ist ein Kind, um das sich der Elternprozess nach dessen Ableben noch nicht gekümmert hat.

Die Grafikkarte selber hat einen Speicherbereich in den man Daten hineinschreiben kann.

Bei den alten "Heimcomputern" oder Computern bis in die 1980er Jahre schrieb man direkt in den Speicher der zur Bildausgabe verwendet wurde.

Die Darstellung der Bits hing davon ab wie die Grafikausgabe aufgebaut war.

Im text-Modus liegen die ASCII (oder andere Codierung) direkt im Grafikspeicher. Zur Ausgabe übersetzt das Zeichen-ROM das Byte in eine Pixelfolge für den Bildschirm. Beim PC sind immer zwei Bytes hintereinander für jedes Zeichen auf dem Bildschirm zuständig. Das erste legt die Farbe fest. 4 bits für Vordergrundfarbe (16 verschiedene Farben), 3 bits für die Hintergrundfarbe (8 Farben), das 8. Bit hat verschiedene Bedeutungen die von der Grafikkarte und deren Modus abhängen. Das kann die Schrift blinken lassen oder unterstreichen. Bei CGA/EGAVGA bedeutet es in einem bestimmten Modus "hell" oder "dunkel" und erweitert die 8 Farben wieder auf 16, dafür kann nicht geblinkt werden. Bei vielen Heimcomputern (C64 z.B.) gibt es ein extra Farb-RAM das extra behandelt wird. Da kann man nur indirekt ran und das enthält dann jeweils 4-bit Vordergrund und Hintergrundfarbe.

Bei "billiger Hardware", also Heimcomputern musste die Grafikausgabe und Prozessor sich das System teilen, der Prozessor konnte also nur z.B. jeden zweiten Takt arbeiten. Beim C64 hat man einen Trick verwendet, hier wird der Takt doppelt genutzt. Bei positiver Flanke darf die CPU, in der negativen während die CPU noch verarbeitet darf der Grafikchip an den Speicher. Dadurch ist die Grafikausgabe scheinbar unabhängig und der Prozessor hat volle Geschwindigkeit. Deswegen ist der C64 anderen Geräten aus der Zeit so überlegen.

Beim PC oder anderen "hochwertigen" Computern wird ein Spezialspeicher verwendet. Der hat zwei Sätze Address- und Datenleitungen. Von der einen Seite kann er von der CPU gelesen und beschrieben werden während die Grafikkarte den völlig unabhängig mit eigenem Takt auslesen kann.

Bei Grafik muss natürlich jeder Pixel getrennt gesetzt werden. Wie genau hängt von der Grafikhardware ab. Früher war es üblich, immer 8 in einer Zeile hintereinander liegende Pixel in ein Byte zu gruppieren. Jedes Bit steuert einen Pixel. Für Farbe hat man dann mehrere Bytes, bei EGA sind es drei. Damit kann man getrennt Rot/Grün/Blau ein und ausschalten und hat dann 8 mögliche Farben. Je nach Modus hat man dann ein 4. Byte für die Helligkeit, "Intensiv". Im CGA/EGA Anschluss steuert das eine 4. Leitung für die Helligkeit und man hat dann insgesamt 16 Farben. In anderen Modi lässt es den Bereich blinken oder ist gar nicht vorhanden. Bei CGA gibt es nur zwei Bytes. Welche Leitung oder Leitungen die im Monitor ansteuern, das hängt vom Modus ab. Jeder Pixel kann 4 Farben annehmen. Man kann wählen zwischen

  1. Schwarz - Magenta - Cyan - Weiß
  2. Schwarz - Rot - Grün - Gelb

Das macht Spiele im CGA Modus besonders hübsch :D Text hat bei CGA natürlich "alle" Farben, also 16, Grafik wegen dem kleinen Speicher nicht!

Bei VGA gibt es den 256 Farben Modus. Hier hat jeder Pixel sein eigenes Byte. Diese Nummer wird dann über eine Farbtabelle übersetzt. Diese tabelle kann man selber verändern und hat die Wahl aus 16,7 Millionen Farben, der Bildschirm kann aber immer nur 256 davon gleichzeitig darstellen. VGA Karten brauchen deswegen richtig viel Speicher, fast ein halbes Megabyte - und der Ur-PC hatte gerade mal 16 Kilobyte.

Auch der C64 hat viel zu wenig Speicher um seine 16 Farben beliebig darzustellen. Mit seinen 320×200 pixeln bräuchte der 32kB, die Hälfte seines Speichers! Deswegen unterteilt der den Bildschirm in 8×8 Pixel Blöcke. Jeder Pixel kann "an" oder "aus" sein. Im farb-RAM steht dann welche Farbe für "an" oder "aus" für den Block verwendet wird. Dadurch kann jeder Buchstabe seine eigene Farbe haben, bei Grafik hat jeder Block nur zwei verschiedene Farben.

Moderne Grafikkarten arbeiten ganz anders, auch wenn die noch zum alten CGA/EGA/VGA kompatibel sind. Der Ausgabespeicher ist von der CPU aus nicht erreichbar. Die Grafikkarte erzeugt die Daten für die Ausgabe selber. Dazu nimmt die die alten Speicherbereiche und rechnet die darin vorhandenen Daten für die Ausgabe um. Natürlich nur, wenn die in einem der Klassichen Modi läuft.

Moderne Betriebssysteme laufen schon lange nicht mehr in den klassichen Videomodi. Hier werden einzelne Speicherbereiche zu einem Bild zusammen gesetzt. So kann jedes Fenster, der Desktop(hintergrund), Taskbar usw. einen eigenen Speicherbereich in der Grafikkarte bekommen zusammen mit Anweisungen an welcher Bildschirmposition die Teile liegen und welche Ebene die haben. Verdeckt man ein Fenster mit einem anderen, so geht dessen Inhalt nicht verloren. Früher konnte man bei einem überlasteten Computer sehen wie man den Inhalt aus einem Fenster wischte und wie der das hinterher Stück für Stück wieder neu aufbaute.

Auch werden die Daten von Bildern und Videos teilweise einfach nur der Grafikkarte übergeben und der wird dann gesagt wo das auf dem Bildschirm erscheinen soll bzw. in welchem Fenster. Den Rest macht die Grafikkarte dann selber. Auch kann die Grafikkarte Daten von woanders annehmen. zum Beispiel kann eine TV-Karte das Fernsehbild direkt in der Grafikkarte decodieren lassen ohne den rest des Systems zu beeinflussen. War früher ein großer Teil der CPU mit Fernsehen beschäftigt ist heutzutage die Mehrbelastung der CPU kaum oder gar nicht mehr messbar!

Woher ich das weiß:Berufserfahrung

In alten Systemen war es so in neueren Systemen sind diese Pixel im Video Ram auf der GPU gespeichert.

DirectX usw. sind im Grunde nur standarstisierte Schnittelstellen um mit der Grafikkarte zu Kommunizieren. Es würde zwar theoretisch auch ohne DirectX usw. gehn ist aber mit diesen Schnittstellen einfacher daher verwendet das OS selbst ebenfalls zum Teil diese Bibliotheken.


Usedefault 
Beitragsersteller
 02.08.2020, 22:05

Ist dieses "COM" etwas windowsspezifisches? Ich habe gesehen, wenn man so ein DirectX Interface erstellt hüpft es bei dem Create in die apphelp.dll als erstes?

Angenommen man erstellt ein DirectX Interface als Schnittstelle, braucht man den sowas wie eine DirectX Runtime oder woher nimmt man die Funktionen (auch als reiner Anwender)?

PeterKremsner  02.08.2020, 22:12
@Usedefault

Ja COM ist ein Windowsstandart. Es ist quasi ein Layoutstandard für DLLs damit diese DLLs zB von verschiedenen Compilern usw verwendet werden können.

Es ist bei Sprachen wie C und C++ so, dass DLLs oder Shared Libraries immer das Problem haben, dass das Memorylayout zwischen den Compilern anders ist. Eine normale DLL die mit dem Gcc erstellt wurde ist zB nicht mit dem Visual Studio C Compiler kompatibel und COM ist ein Standard um das dennoch zu ermöglichen.

Auf dem System muss natürlich die DirectX Runtime installiert sein und das Interface kannst du dann über die entsprechenden DLLs verwenden.

Usedefault 
Beitragsersteller
 02.08.2020, 22:16
@PeterKremsner

Weil beim einen Compiler die VTable oder so oben oder unten sein kann und die exe vom anderen Compiler findet die dann nicht? Und COM Dlls sind genormt bzw. wird das Interface einfach durch den Pointer bereitgestellt und befindet sich im Speicher der Runtime?

Etwas anderes was mich sehr interessieren würde: Dlls sind ja da damit sie nur einmal geladen von vielen Prozessen verwendet werden können.

Warum hat dann jeder Prozess seine eigene Kernell32.dll im Speicher weil MSVC schon die .libs einbindet vorweg und die dann automatisch alle geladen werden?

PeterKremsner  02.08.2020, 22:28
@Usedefault

Genau es gibt hald kein standartisiertes Memorylayout im C Standard womit jeder Compiler da selbst etwas machen kann. Zum Teil sind die Layouts ja auch zwischen Compilerversionen nicht mal kompatibel.

Das genaue Layout von COM Dlls kenn ich nicht, aber es wird eben ein Teil des Memorylayouts so gestaltet, dass dieser einen Abstraktionslayer der DLL bildet und dieser Layer hat immer den selben Aufbau.

Genau eine DLL wird im Optimalfall nur einmal geladen.

Eine DLL ist aber nicht direkt im Speicher des Programms sondern wird dort reingemapped. Sprich es gibt einen Memorybereich in dem die DLL geladen wird und dieser Memorybereich wird dann in den Memorybereich der Anwendung gespiegelt. Womit die DLL zwar nur einmal im Ram vorhanden ist, aber jeder Prozess die DLL so betrachten kann als wäre sie Teil des eigenen Memorylayouts.

.libs bezeichnet bei C meistens statische Bibliotheken also hier wird die Bibliothek direkt ins Programm eingearbeitet und nicht wie eine DLL gemapped. Viele DLLs haben aber eine statische Bibliothel dabei um das Handling mit der DLL zu vereinfachen. In der statischen Bibliothek ist dann eben das Zeugs um die DLL zu laden zu Mappen und einfach wie normale C Funktionen verwenden zu können. Das macht im Endeffekt nur das Arbeiten mit der DLL angenehmer.

Usedefault 
Beitragsersteller
 02.08.2020, 22:41
@PeterKremsner

Aber mir ist aufgefallen, wenn man eine ganz simple WindowsAnwendung startet dann werden schon sämtliche Dlls geladen? Ich dachte es hat damit zu tun dass in der Befehlszeile vom Kompiler die .libs eingebuden werden? In der kernel32.lib steht ja nicht der Source code der dll?

Oder werden die ganzen Dlls geladen weils in diesem Registry Pfad da sind und es hat nichts damit am Hut?

Usedefault 
Beitragsersteller
 02.08.2020, 22:45
@PeterKremsner

Sobald man aber eine Dll überschreiben will oder hooken erzeugt Windows automatisch eine Kopie oder? Weil sonst würden ja alle anderen Programme die diese Dlls benutzt falsche Offsets haben usw.?

PeterKremsner  02.08.2020, 22:45
@Usedefault

Nein aber du brauchst einfach bestimmte DLLs um mit dem Betriebssystem zu kommunizieren und diese werden bei jedem Programm von Haus aus eingebunden damit Funktionen wie zB Print in der Standardlibrary von C funktionieren können.

In der lib steht nicht der Quellcode zur DLL aber der Interfacecode zur verwendung dieser DLL.

PeterKremsner  02.08.2020, 22:50
@Usedefault

Nein eine DLL wird nicht kopiert daher ist einen DLL auch zwingend ein Positionindependend Code. Das Programm muss nur die Einsprungadresse der DLL kennen der Code in der DLL kann an jeder Position im Speicher stehen. Positionindependent eben.

Usedefault 
Beitragsersteller
 02.08.2020, 23:12
@PeterKremsner

Ja das ist eh klar weil alles Offsets sind plus Badeadress.

Aber angenommen du veränderst die Kernell32.dll in deiner Anwendung. Dann kann es ja nicht sein, dass plötzlich jede Anwendung eine andere Kernell32.dll verwendet? Ich glaube sobald man die Protection von der Dlls ändert dann erzeugt Windows eine Kopie der DLL.

PeterKremsner  02.08.2020, 23:22
@Usedefault

Wie meinst du wenn du die DLL veränderst? Der Programmcode selbst ist ja nicht selbstmodifizierend und der Stack und Ram sind ja in den Prozessen etwas eigenes.

Also man ändert ja nicht den Code der DLL selbst zur Laufzeit.

Das vollständige Kopieren der DLL würde ja auch den Sinn und zweck von dieser zerstören.

Usedefault 
Beitragsersteller
 02.08.2020, 23:30
@PeterKremsner

Ja aber wenn du mit Debugger eine Exe aufmachst, siehst du sagen wir 10 Windows DLLs die benötigt werden, wenn Sleep oder CreateWindow verwendet wird.

Wenn du jetzt in diesem 4GB Adressraum den der jeweilige Prozess hat die Kernell32.dll veränderst, hat dann nur der Prozess eine andere Dll oder JEDER Prozess?

Und wenn es nur den eigenen Prozess betrifft, dann ist es ja nicht dieselbe DLL wie in den anderen Prozessen? Dann muss sie mehrmals im RAM auch sein?

PeterKremsner  02.08.2020, 23:36
@Usedefault

Du kannst DLLs ja im allgemeinen auch nicht verändern. Der Bereich indem die DLL gemapped ist ist quasi readonly und du kannst sie nicht so überschreiben. Es gibt eigene DLLs die so etwas erlauben die sind dann eben wirklich kopiert und nicht nur abgebildet.

Btw eine DLL ist im Grunde nur der Code selbst Variablen usw. Sind ja im Heap und am Stack und dieser Speicherbereich sind natürlich immer für jeden Prozess einzigartig.

In der Loadtable der Exe ist die DLL ja im allgemeinen auch nur Einsprungadressen für die Funktionen in dieser DLL.

Das siehst du zB bei Windows mit dem declspec DLL import im C Code schön. Hier wird quasi nur eine Einsprungadresse für die Funktion aus der DLL importiert die ja diese Adressen per DLL Export exportiert.

Usedefault 
Beitragsersteller
 02.08.2020, 23:53
@PeterKremsner

Ja das versteh ich eh nicht ganz:

Warum kennt der Debugger die Namen denen ich meinen Funktionen gegeben habe?

Ich hab grad angeschaut dass

00451BCC |. FF15 80C46C00 |CALL DWORD PTR DS:[<&USER32.PeekMessage>; \PeekMessageA

zu

778D9D30 > 8BFF       MOV EDI,EDI

in der User32.dll huepft?

Wie kommt der Compiler auf diese Zahl 80C46C00 ? Und abgesehen davon versteh ich grad gar nicht wie das dann auf 778D9D30 kommt.

PeterKremsner  02.08.2020, 23:57
@Usedefault

Die Namen der Funktionen stehen in den Debuginformationen. Ob sie da sind oder nicht kommt drauf an was du dem Compiler sagst.

Diese Zahlen sind nehm ich mal an Speicheradressen.

Usedefault 
Beitragsersteller
 02.08.2020, 23:59
@PeterKremsner

Ja wie kommt man vom Maschinencode

FF15 80C46C00

auf CALL DWORD PTR DS:[<&USER32.PeekMessage>; \PeekMessageA ?

und landet dann auf der Adresse 778D9D30 ?

PeterKremsner  03.08.2020, 00:07
@Usedefault

Ich kann den Debugtrace den du hier geschickt hast nicht ganz entschlüsseln. Auf die Adressen kommt man aber einfach indem der Compiler ja weiß wo er diese eben in den Adressbereich legt.

Bei Calls auf DLLs hast du eben eine Vectoradresse die zunächst geladen wird und dann wird ein Call in diese Funktion gemacht. Die Vektortabelle ist dabei natürlich in einem anderen Speicherbereich als die DLL selbst bzw der Programmcode und mit dem Call Befehl wird dann eben ein Einsprung in eine Methode gemacht.

Einen Teil der Adressen wie zB die eigener Funktionen bestimmt ja der Compiler bzw genauer der Linker. Eine Teil der Adressen lädt das Programm nachträglich aus bestimmten Tabellen im Speicher. Bei einer DLL ist zweiteres der Fall.

Das FF15 könnte eventuell der Maschinencode für das CALL sein aber ich kenne die Codes für x86 bzw x64 CPUs nicht auswendig.

Usedefault 
Beitragsersteller
 03.08.2020, 00:18
@PeterKremsner

Ich verstehe von deinen Antworten genau gar nichts und denke wir reden aneinander vorbei.

Schau ich mache mit Debugger eine .exe auf und dann unter View Memory oder View Executables sehe ich eine Liste mit Dlls.

Und wenn ich im Programmcode wo PeekMessage aufrufen dann huepft die CPU auf die Adresse 778D9D30.

Und das ist bei jeder .exe die Adresse wo PeekMessage liegt und auch die Size und die Base der USER32.dll ist immer gleich.

Wenn jetzt eine andere DLL später dazugeladen wird, die dieselbe Base hat wie die USER32.dll dann wird m. M. die neue rellociert.

Aber der Programmcode der .exe ist ja immer derselbe und verwendet statische Verweise in dem Fall FF15 80C46C00, und in der .exe muss man ja die Funktion auch finden wenn die DLL relloziert wurde?

PeterKremsner  03.08.2020, 00:25
@Usedefault

Naja ich verstehe nicht ganz worauf du hinaus willst.

Du hast eine Vektortabelle und dort steht eben drinn unter welche Adresse die Einsprungadresse der DLL ist.

Das Betriebssystem wird ja beim Programmstart hoffentlich wissen in welchen Bereich es die DLL reinmapped und dementsprechend den Memoryvector anpassen.

Wenn dein Programm jetzt zB eine Funktion aus der DLL braucht sieht es in der Vectortabelle nach wo diese Funktion ist holt sich die Speicheradresse und macht dann einen Call dorthin. Dahingehend ist es der Exe ja egal wo die DLL ist solange die Vektoren stimmen.

Usedefault 
Beitragsersteller
 03.08.2020, 00:34
@PeterKremsner

Das heißt wenn ich in einem Roh Prozess die User32.dll lade dann schaut die Funktion LoadLibrary nach wo der Einsprungspunkt ist und in den 4GB RAM den man mit Debugger sieht ist dass dann immer eine bestimmte Adresse?

Wenn ich mir die Dll anschaue die der Prozess geladen hat dann steht

Executable modules, item 25

 Base=778C0000

 Size=00154000 (1392640.)

 Entry=778D26C0 USER32.UserClientDllInitialize

 Name=USER32  (system)

 File version=6.3.9600.16384 (winblue_rtm.130

 Path=C:\Windows\SYSTEM32\USER32.dll

Meinst du das mit Memoryvector?

PeterKremsner  03.08.2020, 00:41
@Usedefault

Jein der Memoryvector ist in einem Speicherbereich.

Ist in etwa so wie ein Funktionspointer in C.

Aber hier siehst du zwei Speicherbereiche die Baseadress und die Entry Funktion. Ich nehme mal an dass an der Adresse 80C46C00 die Adresse der Funktion in der DLL oder etwas ähnliches stehn wird. Womit das Program weiß wo die DLL ist.

Es gibt btw auch mehre Arten eine DLL zu laden. Beim Implicit Loading wird die DLL gleich zu Programmstart vom OS mitgeladen es kann durchaus sein, dass der Compiler für diese Bibliotheken feste Loadpoints angibt und daher zur Compiletime schon weiß wo die Baseadress sein wird. So genau hab ich mich aber mit dem Linking von DLLs noch nicht auseinandergesetzt.

Bei Loadlibrary bekommt man natürlich direkt die Einsprungadresse in die DLL vom Betriebssystem.

Usedefault 
Beitragsersteller
 03.08.2020, 00:51
@PeterKremsner

Ja aber ich versteh es trotzdem nicht.

Also wenn ich LoadLibrary verwende und dann bekomm ich vom Betriebssystem den Entrypoint und dann rufe ich weil ich das Offset weiß oder den Namen in der Exporttable eine Funktion auf, verlasse ich dann meinen virtualen RAM bereich? Weil laut Debugger springt es in eine Adresse in meinem 4GB Adressraum also auf 778D9D30 und DORT ist PeekMessage dann.

Aber es springt nicht irgendwo in einen realen physischen RAM Bereich wo dann alle Prozesse hinspringen? Sodass es logisch wäre mit DLL ist nur einmal im RAM?

PeterKremsner  03.08.2020, 00:56
@Usedefault

Die 778D9D30 ist eine virtuelle Ram Adresse. Die Physikalische ist eine andere.

Sprich die DLL kann in einem Prozess auf 778D0000 gemapped sein und im nächsten auf zB 78D9D30 allerdings zeigen diese beiden Adressen auf den selben physikalischen Ram.

Usedefault 
Beitragsersteller
 03.08.2020, 01:14
@PeterKremsner

Ah das ist mal was neues! Was genau hat es auf sich mit dem Mappen?

PeterKremsner  03.08.2020, 11:02
@Usedefault

Naja der RAM in einem PC ist in sogenannte Segmente (auch Pages genannt) aufgeteilt. Diese Segmente werden als RAM einer Anwendung zur Verfügung gestellt und dementsprechend vom physikalischen auf den virtuellen Adressraum gemapped.

Wenn also das Programm zB in seinem Virtuellen Adressraum auf die Adresse 80C00000 springt, dann gibt es im Prozessor die sogenannte MMU. Hier ist hinterlegt welche virtuellen Segmente auf welche Realen Segmente gemapped sind.

Die MMU sieht dann von diesem Prozess eine Anfrage an diesen Speicherbereich, erkennt, dass dieser Speicherbereich in einem Segment mit der Basisadresse 80000000 liegt und weiß, dass dieses Virtuelle RAM Segment im physikalischen Arbeitsspeicher im Bereich 7000 96AB 0000 0000 liegt. Sprich sie schreibt die Adresse 80C00000 (Also 80000000 mit Offset C00000) auf 7000 96AB 00C0 0000 um. Die physiklische RAM Adresse hat also relativ wenig mit der virtuellen Adresse zu tun.

Defakto muss nicht mal jede Virtuelle Adresse exisitieren und ein Teil der Virtuellen Adressen muss auch nicht im RAM sein sondern kann auch in der Auslagerungsdatei auf der Festplatte sein. Das erkennt man auch einfach daran, dass jedes Programm 4GB Virtuellen Addressraum erhält, welcher nur diesem einen Prozess gehört und andere Prozesse haben auf diesen keinen Zugriff. Wenn das wirklich alles physikalischer Speicher wäre würden 10 Programme schon 40GB Ram benötigen. Die Segmente werden also auch vom OS erst dann gemapped und wirklich zugeteilt sofern sie verwendet werden.

Wenn jetzt in diesen Virtuellen Adressräumen von unterschiedlichen Programmen das selbe Physikalische RAM Segment gemapped ist, dann spricht man hier auch von Shared Memory und eine static DLL ist quasi in einem Readonly Shared Memory Bereich. Daher existiert diese zwar nur einmal im Physikalischen Ram, sie wird aber in jedem Prozess auf einen eigenen Bereich im Virtuellen RAM gemapped.

Btw von diesen Begriffen Segment und Page kommen auch die Begriffe Segmentation Fault, also das lesen oder schreiben auf ein nicht zugeteiltes Ram Segment im Virtuellen Adressraum, oder der Begriff Pagefault bzw in Deutsch Seitenfehler, also der Fehler der Auftritt wenn die entsprechende Page nicht im Physikalischen Ram sondern auf der Festplatte ist und nachgeladen werden muss.

Usedefault 
Beitragsersteller
 03.08.2020, 17:51
@PeterKremsner

Und wenn ich in meiner DLL im virtuellen Raum die USER32.dll ändere, was passiert dann? Dann muss doch eine Kopie erstellt werden sonst ändere ich ja auch die Physikalischen Adressen oder was ändere ich sonst wenn keine Kopie?

PeterKremsner  03.08.2020, 20:13
@Usedefault

Du kannst die DLL im Speicher nicht ändern. Eine DLL zumindest eine statische ist in einem Readonly Memory Bereich.

Wenn du es dennoch schaffst den Code im Speicher zu überschreiben, dann betrifft diese Änderung auch alle anderen Prozesse die diese DLL nutzen.

Im Falle von nicht statischen DLLs ist die DLL allerdings direkt im Speicher des Programms geladen und nicht im RAM geteilt, die USER32.dll sollte allerdings immer statisch eingebunden sein.

Usedefault 
Beitragsersteller
 03.08.2020, 21:31
@PeterKremsner

Gibt es bei LoadLibrary sowas wie einen Parameter ob man statisch oder dynamisch ladet?

Ich hake nur nach, weil ich in einem Gamehacking Forum mal gelesen habe, dass Windows die Dll automatisch kopiert wenn man sie im Virtual Memory eines Prozesses ändern will.

PeterKremsner  03.08.2020, 21:46
@Usedefault

Das ist ein Flag im Header der DLL selbst. Das kann schon sein, dass Windows das macht um eine Codeinjektion zu verhindern. Aber dennoch dynamischen Code zu erlauben.

Also ich kann mir vorstellen, dass Windows beim schreiben in so eine Readonly Page die Page kopiert anstatt eine Fehlermeldung zurück zu geben. Die Page selbst bzw der Shared Memorybereich der DLL ist aber auch in dem Fall nach wie vor Readonly.

Für gewöhnlich sollte jedoch kein Prozess sofern er normal Funktioniert nicht versuchen in diesen Speicherbereich zu schreiben.

Usedefault 
Beitragsersteller
 03.08.2020, 21:48
@PeterKremsner

Wobei dann vermutlich die MMU gleich auf den kopierten Speicherbereich zeigt.

PeterKremsner  03.08.2020, 21:55
@Usedefault

Genau.

Also ich gehe davon aus, dass das Intern zu einem Fehler führt und Windows diesen Fehler behebt indem es die Memorypage kopiert und die neue Page statt der alten an die selbe Stelle im Prozess Mapped.

Es gab diesbezüglich in älteren Windowsversionen mal einen Exploit, wenn ich mich richtig erinnere, welcher erlaubt hat, dass man die MMU dazu bringen kann Speicher aus dem eigenen Adressspace in den eines anderen Programm zu mappen. Das hat dann im Endeffekt dazu geführt, dass man Schadcode in ein anderes Programm einbringen konnte.

Zum Teil geht das auch noch und nennt sich DLL Injection.

Usedefault 
Beitragsersteller
 03.08.2020, 22:22
@PeterKremsner

  HANDLE hThread = CreateRemoteThread(

TargetProcess, 

NULL, 

NULL, 

(LPTHREAD_START_ROUTINE)LoadLibAdress, 

(LPVOID)RemoteString,

NULL, 

NULL);

damit geht auch ganz einfach indem man einen anderen Prozess dazu bringt "LoadLibrary" auf die eigene DLL zu callen oder so. Auf alle Fälle klappt das. Aber ich bin froh das Windows dem Administrator wirkliche Adminrechte zukommen lässt und nicht einen komplett einschränkt wie Android.

Mit der DLL Injection brauche ich dann einfach nur mehr Pointer und Parameter und kann Funktionen aufrufen.

PeterKremsner  03.08.2020, 22:34
@Usedefault

Mit den Exploit vorher wars ohne Adminrechte möglich und es hat bei allen Processen funktioniert. So konnte man recht einfach Schadsoftware auf Ring0 integrieren.

Das mit der DLL Injection ist ein zweischneidiges Schwert. Zum einen eröffnet es Möglichkeiten zum anderen ists das beste was einem Hacker passieren kann.

So kannst du zB leicht Schadsoftware in laufende Programme integrieren und damit den Nutzer und sogar die Antivirensoftware täuschen.

Usedefault 
Beitragsersteller
 03.08.2020, 22:48
@PeterKremsner

Mich interessiert das erstellen von Schadsoftware eigentlich gar nicht sondern nur das Modifizieren von Programmen nach Reversing.

PeterKremsner  03.08.2020, 22:56
@Usedefault

Schon klar dennoch muss man als Entwickler von Software an die Interessen aller denken und da gehört nun mal auch das Schließen von Sicherheitslücken dazu