0

Here’s the code:

__declspec ( naked ) void nseel_asm_assign(void)
{
  __asm 
  {
    fld qword ptr [eax]
    fstp qword ptr [ebx]
  }
}
__declspec ( naked ) void nseel_asm_assign_end(void) {}

The code that consumes them crashes. The debugger shows the addresses are OK, e.g.

&nseel_asm_assign   0x0f45e4a0 {vis_avs.dll!nseel_asm_assign(void)} void(*)()
&nseel_asm_assign_end   0x0f45e4b0 {vis_avs.dll!nseel_asm_assign_end(void)} void(*)()

However, when the address of these functions is taken by the actual C code not by the debugger, it stops being correct and the consuming code crashes because the size is negative:

    fn  0x0f455056 {vis_avs.dll!_nseel_asm_assign}  void(*)()
    fn_e    0x0f45295f {vis_avs.dll!_nseel_asm_assign_end}  void(*)()

The underscored functions contain just a single instruction, e.g. jmp nseel_asm_assign

How do I get the addresses of the real functions, without the underscore?

Update: in case you wondering why I wrote code like this, it wasn’t me, it’s third party, and it worked just fine when built with VC++ 6.0.

Soonts
  • 20,079
  • 9
  • 57
  • 130
  • "because the size is negative" how is that possible given that `size_t` is unsigned? – Dai Dec 04 '18 at 07:29
  • 1
    Also, exactly what toolchain and compiler (and version) are you using? And what platform are you targeting? – Dai Dec 04 '18 at 07:30
  • @Dai The consuming code is a JIT compiler, it calculates size by subtracting the addresses of these 2 functions then copies the function's code. Win32, VC++ 2017. – Soonts Dec 04 '18 at 07:33
  • `naked` means the compiler doesn't emit a `ret` instruction for you, or any setup to put pointers into registers. You don't show how you "consume" this code, but all you'll get is a label on 2 asm instructions which then fall through into whatever's next. – Peter Cordes Dec 04 '18 at 07:34
  • Correct me if I'm wrong, but C and C++ do not guarantee the location or even relative-ordering of functions in an executable's loaded image. Using this `foo()`, `foo_end()` trick would then rely on a vendor extension or undefined behavior. I don't think C even requires that functions are contiguous in memory either (e.g. an optimizer could combine two similar functions together by stitching them with an unconditional `jmp`). – Dai Dec 04 '18 at 07:41
  • 2
    You need to compile with /Zi instead of /ZI to defeat the trick MSVC++ uses to support incremental linking and edit+continue. Look at the reported address, you should see a JMP instruction there now that jumps to the real code. Modifying the JMP target is what allows the linker and the debugger to replace the function at will. That doesn't help you to measure the function size and move code. – Hans Passant Dec 04 '18 at 07:47
  • @HansPassant Edit & continue is switched off, I’m building with `/Zi` – Soonts Dec 04 '18 at 07:53
  • @Dai you’re right, but with VC6, the code worked fine for many millions of people, it’s Winamp. – Soonts Dec 04 '18 at 07:56
  • @PeterCordes I’ve linked to the complete repository. OK here’s a deep one: https://github.com/visbot/vis_avs/blob/master/avs/ns-eel/nseel-compiler.c – Soonts Dec 04 '18 at 07:58
  • Can you show what addresses you get for these functions in the calling C code (for the actual functions and not the underscored ones). Also is there supposed to be a return instruction somewhere? – Ajay Brahmakshatriya Dec 04 '18 at 14:04
  • @AjayBrahmakshatriya There’s no calling code. JIT compiler only uses these naked functions as a building blocks to generate the code. Here’s how: https://github.com/visbot/vis_avs/blob/master/avs/ns-eel/nseel-compiler.c#L500 – Soonts Dec 04 '18 at 14:50
  • @AjayBrahmakshatriya BTW it mostly works now. Besides unwrapping the jumps inserted by the modern compiler, I also had to trim the padding 0xCC = `__asm int 3` bytes inserted by the compiler for 16-bytes alignment of the functions. Old compilers didn’t do that, maybe ‘coz old CPUs didn’t have micro-ops cache. – Soonts Dec 04 '18 at 14:54
  • @AjayBrahmakshatriya Also VirtualProtect to allow execution. – Soonts Dec 04 '18 at 14:56

1 Answers1

0

Here’s how to get the real address.

static void* unwrapJumpAddress( void *f )
{
    const uint8_t* pb = (const uint8_t*)f;
    if( *pb == 0xE9 )   // JMP: http://felixcloutier.com/x86/JMP.html
    {
        const int offset = *(const int*)( pb + 1 );
        // The jump offset is relative to the start of the next instruction.
        // This JMP takes 5 bytes.
        return (void*)( pb + 5 + offset );
    }
    return f;
}
Soonts
  • 20,079
  • 9
  • 57
  • 130