turbo
turbo

Reputation: 1263

Passing a reference to a string works only if copied into a temporary variable first

I'm currently writing code for a size-coding entry. I say this because it causes some restrictions as to what I can do. So if you read the code and ask yourself e.g. "why use wsprintf, not just sprintf", it has a reason. No CRT can be linked. Only WinAPI libs are allowed. The objective is to generate the least amount of assembly instructions going into the linker. With that said, onto the problem.

Prerequisites

This macro:

#define GLExt(a) a(wglGetProcAddress(#a))

is used as a shorthand to call OpenGL extension functions previously declared. This problem only concerns one function, which has this signature:

extern "C" {
    typedef void(__stdcall*glShaderSource)
      (unsigned shader, int count, char** string, const int *length);
} 

The Problem

I load a string constant as a template and use wsprintf to fill in the values. In this small example of the issue it's simply the elapsed time in ms. The following code works fine. I've inserted the generated assembly above the respective LOCs:

const auto starttime = GetTickCount();

do {
    char *pflog;

    //~ call    DWORD PTR __imp__GetTickCount@0
    //~ sub eax, DWORD PTR _starttime$1$[esp+24]
    //~ push    eax
    //~ push    OFFSET <text from data>
    //~ push    DWORD PTR _pflog$1[esp+32]
    //~ call    DWORD PTR __imp__wsprintfA

    wsprintf(pflog, "void main(void){int foo=%d;}", GetTickCount() - starttime);

    //~ mov eax, DWORD PTR _pflog$1[esp+36]
    //~ add esp, 12                 ; 0000000cH
    //~ mov DWORD PTR _blorg$2[esp+24], eax

    char *blorg = pflog;

    //~ lea eax, DWORD PTR _blorg$2[esp+24]
    //~ push    0
    //~ push    eax
    //~ push    1
    //~ push    ebp
    //~ push    OFFSET <glShaderSource>
    //~ call    esi
    //~ call    eax

    GLExt(glShaderSource)(s, 1, &blorg, 0);
}

However, if I try to use pflog directly, the code compiles without complaint but crashes on the first call to glShaderSource. Here's is the annotated version of that:

const auto starttime = GetTickCount();

do {
    char *pflog;

    //~ call    DWORD PTR __imp__GetTickCount@0
    //~ sub eax, DWORD PTR _starttime$1$[esp+24]
    //~ push    eax
    //~ push    OFFSET <text from data>
    //~ push    DWORD PTR _pflog$1[esp+32]
    //~ call    DWORD PTR __imp__wsprintfA
    //~ add esp, 12

    wsprintf(pflog, "void main(void){int foo=%d;}", GetTickCount() - starttime);

    //~ lea eax, DWORD PTR _pflog$1[esp+24]
    //~ push    0
    //~ push    eax
    //~ push    1
    //~ push    ebp
    //~ push    OFFSET <glShaderSource>
    //~ call    esi
    //~ call    eax

    GLExt(glShaderSource)(s, 1, &pflog, 0);
} while (1);

I'm a bit stumped. As far as I can tell the variables are accessed in the same way at the same offsets in both versions (apart from the copy). Why is one version unexpectedly crashing, or rather: Why does the temporary variable make a difference?

Upvotes: 0

Views: 78

Answers (1)

nvoigt
nvoigt

Reputation: 77295

char *pflog;

wsprintf(pflog, "void main(void){int foo=%d;}", GetTickCount() - starttime);

Whatever happens after this is doomed to fail. Unfortunately, it did not fail right in the second line. You are writing to random memory you do not own. You cannot just write a string to an uninitialized character pointer. You need to allocate the memory behind it, by either using new (preferred in C++) or any of the alloc functions.

Upvotes: 1

Related Questions