C: Variadische Funktionen "nachbilden"

16/12/2010 - 21:29 von Thomas Rachel | Report spam
Hallo,

ich habe derzeit das Problem, eine Funktion, von der es zwar eine Variante

int32_t LStrPrintf(char * pattern, ...);

gibt, aber keine der Form

int32_t LStrPrintfV(char * pattern, va_list ap);

. Letzte benötige ich aber aus diversen Gründen. Also habe ich mich
entschlossen, sie nachzubauen. Dafür sind mir zwei Herangehenseweisen
eingefallen: 1. Stackframe ab va kopieren, 2. Stack von Spitze bis va
wegsichern und spàter wiederherstellen.

Mir ist bewußt, daß es sich bei beiden Fàllen um einen sehr wüsten Hack
handelt, der hàtte vermieden werden können, wenn der Anbieter der
Library so schlau gewesen wàre, eine LStrPrintfV() nach obigem Muster
bereitzustellen. War er aber leider nicht :-(

Compiler ist gcc (MinGW) unter Windows.

1. Welcher der beiden Möglichkeiten sollte man den Vorzug geben? Ich
habe jetzt mal #1 implementiert (s.u.), mit ein wenig Bauchschmerzen
wegen der Stackverschwendung. War aber im Endeffekt einfacher als
malloc/memcpy/... .

2. Sollte man die Funktion komplett in Assembler lösen (sauberer) oder
genügt es, an strategischen Stellen mit asm() %ebp abzufragen bzw. %esp
zu setzen und _LStrPrintf aufzurufen? Nicht, daß mir in der n+1.
Folgeversion des Compilers die Geschichte auf die Füße fàllt...

3. Ist ein Stack-Alignment auf 16 Bytes zwingend (ABI) oder nur
empfehlenswert?

4. Sonstige Vorschlàge?


TIA,

Thomas




/*****
* It is time again for a RANT about the stupidity of NI folks:
*
* Instead of providing additionally va_list versions of their
* functions (mainly LStrPrintf), which had cost about 5 mins, they
* force me to do a terribly ugly and unportable hack in order to
* replicate the same behaviour.
* See below.
*****/

int arglen(va_list ap)
{
// Calculate the size of arguments we have.
// Move along the stack frames (%ebp) until we have the one
// containing our "ap".
// The difference of ap and bp is the size of the stack frame,
// which limits the size of the arguments.
void * bp;
asm("movl\t%%ebp, %0":"=g"(bp));
while (bp < (void*)ap) {
bp = *(void**)bp;
//DbgPrintf("bp=%08x",bp);
}
//DbgPrintf("arglen=%d", bp-(void*)ap);
return bp-(void*)ap;
}
MgErr LStrPrintfV(LStrHandle destLsh, CStr cfmt, va_list ap)
{
// How many arguments do we have at maximum?
int len = arglen(ap);

char *src, *dst2, *bufstart;
uintptr_t * dst1;
MgErr ret;
unsigned int modulo;

// buf MUST be the last variable! We want it topmost on the stack,
// containing the arguments for the call below.
// With 0x10, we align it on a 16 byte boundary.
// The frame pointers must be at 0x0, the first parameter is at 0x4.
static const int MODULO = 0x10;
static const int OFFSET = 0x08;
char buf[sizeof destLsh + sizeof cfmt + len + MODULO];
bufstart = buf;
modulo = ((uintptr_t)bufstart) % MODULO;
// Modulo is end digit of bufstart's address: 0..F
// We need: 0: +8, ..., 7:+1, 8: 00, 9: +F, ..., F: +9
bufstart += (MODULO + OFFSET - modulo) % MODULO;
// Alignment done.
//DbgPrintf("buf@%08x, modulo %x - use@%08x", buf, modulo, bufstart);
dst1=(void*)bufstart;
if (dst1)
// Write our arguments into buf.
dst1[0] = (intptr_t)destLsh;
dst1[1] = (intptr_t)cfmt;
// Append the ap arguments into buf.
dst2=(void*)&dst1[2];
src = ap; // ap points to the 1st argument - on x86.
while (len--) {
*dst2++ = *src++;
}

// Move the %esp to point to the start of buf.
// That's why it must be topmost on the stack.
// Then do the call.
__asm__(
"movl\t%1, %%esp;\t"
"call\t_LStrPrintf\t"
:"=a"(ret) // output is (automatically) in %eax - function result.
:"g"(bufstart) // any register, memory or immediate integer
operand is allowed, except for registers that are not general registers.
: "esp" // clobbered
);
return ret;
}

// and now use LStrPrintfV(ret, cfmt, ap) in several functions
// [snip]
 

Lesen sie die antworten

#1 Stefan Kuhr
16/12/2010 - 22:05 | Warnen spam
Hallo Thomas,

On 12/16/2010 9:29 PM, Thomas Rachel wrote:
<snip>
4. Sonstige Vorschlàge?




Nur so als Idee: Du koenntest auf Verdacht hin einen "hinreichend
grossen" Puffer auf dem Stack verwenden, in den Du mit
StringCchVPrintfEx aus strsafe.h (ist bei jedem SDK dabei, sollte also
auch mit nicht-MS-Compilern tun) formatierst. Wenn diese Formatierung
fehlschlaegt, weil der Puffer zu klein war, legst Du einen groesseren
Puffer auf dem Heap an und probierst es nochmal und vergroesserst so
lange den Puffer, bis es funktioniert. Das Ergebnis uebergibst Du dann
der vorhandenen Funktion als reinen Formatstring.

Warum der Hersteller das so gemacht hat? Ich denke mal, die
Implementation von Funktionen mit einem va_list sind eher a bissal
compilerherstellerspezifisch, so dass man sich mit einem Binaerinterface
fuer eine DLL auf nicht-interoperables Terrain begibt. Das ist aber nur
eine Vermutung von mir. Aber auf Anhieb fiele mir auch beispielsweise
kein Windows-API ein, das va_list als Parameter haette, wohl aber welche
mit Ellipsis. Oder mag mich jemand Luegen strafen? :-)

S

Ähnliche fragen