printf in __asm

Cromon

Erfahrenes Mitglied
Hallo miteinander!

Von der Neugier angetrieben als ich das nette Schlüsselwort __asm gesehen habe, bin ich zum Entschluss gekommen, diese Assemblygeschichte mal etwas näher unter die Lupe zu nehmen.

Die ersten Erfolge waren auch sehr schnell da, habe ein Programm geschrieben, dass eine beliebig grosse Liste von Zahlen durchsucht und die grösste zwischenspeichert in einer c-Variable für die spätere Ausgabe mit printf.

Nun habe ich gelesen, dass auch RTL-Funktionen in __asm aufgerufen werden können. Daher habe ich mich entschlossen auch die Ausgabe gleich in __asm via call printf zu vollbringen. Leider bin ich auf ein paar Probleme gestossen.

Meine Theorie war es, zuerst den Wert der Zahl auf den Stack zu pushen und anschliessend die Adresse des Formatstrings, also folgendermassen:
Code:
char fstr[] = "Höchste Zahl: %u\n";
int res;
__asm{
mov res, ebx		; Die grösste Zahl in res speichern
push res
lea eax, fstr
push eax
call printf
pop ebx
pop ebx
}

Leider ist es nun so, dass das Programm jedesmal abstürzt und mir Visual C++ auf das erste pop zeigt mit dem Hinweis, dass das die nächste Anweisung wäre, die ausgeführt würde, hätte es keinen Fehler gegeben.

Folgendes sagt die Aufrufeliste:
ASM.exe!__imp__printf() Unknown
> ASM.exe!main() Zeile 55 C++
ASM.exe!__tmainCRTStartup() Zeile 586 + 0x19 Bytes C
ASM.exe!mainCRTStartup() Zeile 403 C

Hat jemand von euch eine Ahnung, warum dies geschieht?

Gruss
Cromon
 
Hat jemand von euch eine Ahnung, warum dies geschieht?
Du benutzt die RTL vermutlich als DLL (Compilerflag /MD). In dem Fall ist printf ein Zeiger auf einen Eintrag in der IAT (Import Adress Table) und nicht der Zeiger auf die Funktion selbst. Um diese zusätzliche Indirektion aufzulösen, kannst du z.B. call dword ptr [printf] schreiben. Oder du benutzt beim kompilieren /MT statt /MD.

Grüße, Matthias
 
Hallo Matthias!

Vorab mal ein Dankeschön, mit /MT geht es einwandfrei! Das sind immer die kleinen Details, die man überdenkt (es war noch schlimmer, ich hatte /MDD).

Was mich allerdings erstaunt ist, dass strlen offensichtlich nicht in der DLL drin ist, denn der call auf strlen hat auch mit /MDD problemlos geklappt!

Gruss und Dank
Cromon
 
Was mich allerdings erstaunt ist, dass strlen offensichtlich nicht in der DLL drin ist, denn der call auf strlen hat auch mit /MDD problemlos geklappt!
Das kann ich auch nicht ganz nachvollziehen. Aber das sind eben solche Sachen, um die man sich bei der Codierung in Maschinensprache kümmern muss. So gesehen ist C eigentlich schon ziemlich high-level ;-) Im Zweifelsfalle kannst du dir die Anweisung aber auch erst in C schreiben und dir dann die Disassembly anzeigen lassen. Dann siehst du sofort, wie der Aufruf aussehen muss.

Grüße, Matthias
 
Ah, stimmt, das ist ein guter Tipp, in den Disassemblys steht das ja oft einigermassen schön drin, wie der Compiler die entsprechende Routine codiert hat.

Ein kleines Problem hat sich mir jetzt noch gestellt, als ich versucht habe auch die Initialisation des Arrays mit Zahlen via rand in Assemblys zu schreiben. An und für sich funktioniert rand und das verschieben des Rückgabewertes schön ohne Fehler, allerdings ist das Problem, dass anstelle der vollen 2.5 Miarden Zahlen nur zufällig zwischen 3 oder 4 Durchgänge gemacht werden, bis ecx den Wert 0 erreicht hat und somit der Loop abbricht.

Hier der Code, von dem ich spreche:
Code:
		mov ecx, 25000000
		mov ebx, arr
rlp:
		call rand;
		mov ds:[ebx], eax
		add ebx, 4
		dec ecx
		cmp ecx, 0
		jle nxt
		jmp rlp

wobei 'arr' ein Zeiger auf ein genügend grosses Int-Array ist.

Trotz längerem Draufstarren konnte ich den springenden Punkt irgendwie nicht ausmachen, der dafür verantwortlich ist, dass der Loop nicht die volle Anzahl durchläuft.

Gruss
Cromon
 
An und für sich funktioniert rand und das verschieben des Rückgabewertes schön ohne Fehler, allerdings ist das Problem, dass anstelle der vollen 2.5 Miarden Zahlen nur zufällig zwischen 3 oder 4 Durchgänge gemacht werden, bis ecx den Wert 0 erreicht hat und somit der Loop abbricht.
Auch hier hilft der Debugger weiter. Lass dir mal die Register anzeigen (Strg+Alt+G) und schau was beim Aufruf von rand mit ECX passiert. Um das Sichern und Wiederherstellen von Registern muss sich der Aufrufer kümmern.

Grüße, Matthias
 
Stimmt, hat geholfen. Habe übersehen, dass in rand drin vor ret noch ecx verändert wird.

Habe das ganze jetzt etwas abgeändert:
Code:
		push ecx
		call rand;
		pop ecx

Es geht soweit jetzt einwandfrei, aber ich denke, am besten frage ich dich noch, ob es auch korrekt ist, da ich mich in Assemblys noch nicht so wahnsinnig toll auskenne.

Gruss
Cromon
 
Zurück