[X86-32 asm] Nexe crashed during startup when assembling instructions that refer to address in .rodata

20 views
Skip to first unread message

Joe Bruggeman

unread,
Jul 24, 2015, 1:12:39 PM7/24/15
to Native-Client-Discuss
Hi all,

I'm having trouble getting my application to run when I assemble my own x86-32 asm.

The code in question is in a shared library, and code that will cause the crash can look like this:

pand mm1, [somelabel]

I was previously able to modify such code by using RIP addressing, but as far as I know that's a 64-bit thing only.

My nexe validates always and doesn't crash on startup when I remove this line.

Thanks in advance,
Joe

Victor Khimenko

unread,
Jul 24, 2015, 2:41:08 PM7/24/15
to Native Client Discuss
On Fri, Jul 24, 2015 at 8:12 PM, Joe Bruggeman <j.bru...@gmail.com> wrote:
Hi all,

I'm having trouble getting my application to run when I assemble my own x86-32 asm.

The code in question is in a shared library, and code that will cause the crash can look like this:

pand mm1, [somelabel]

I was previously able to modify such code by using RIP addressing, but as far as I know that's a 64-bit thing only.

Indeed. %rip-addressing is not supported in 32-bit mode. That's x86 limitation, not NaCl one. On regular Linux such code works: linker just temporarily removes write-protection from that bit of code and changes this function. On NaCl that does not work (NaCl uses strict W^X model) but code is still there in GLibC loader - thus the observable crash.
 
My nexe validates always and doesn't crash on startup when I remove this line.

The best way to see how this should be handled is to see how compiler handles it. Here is the test code:
static int bar;

int read_bar() {
  return bar;
}

int write_bar(int bar_) {
  return bar = bar_;
}

Now let's see what x86-64 version does:
$ toolchain/linux_x86_glibc/bin/x86_64-nacl-gcc -fPIC -masm=intel -O3 -S -o- test.c 
.file "test.c"
.intel_syntax noprefix
.text
.align 32
.globl read_bar
.type read_bar, @function
read_bar:
.LFB0:
.cfi_startproc
pop r11
mov eax, DWORD PTR bar[rip]
nacljmp r11d,%r15
.cfi_endproc
.LFE0:
.size read_bar, .-read_bar
.align 32
.globl write_bar
.type write_bar, @function
write_bar:
.LFB1:
.cfi_startproc
pop r11
mov eax, edi
mov DWORD PTR bar[rip], edi
nacljmp r11d,%r15
.cfi_endproc
.LFE1:
.size write_bar, .-write_bar
.local bar
.comm bar,4,4
.ident "GCC: (GNU) 4.4.3 20141209 (Native Client r14192, Git Commit 7faaabb9f10e6dcae5f2b799da43e236e65cda95)"
.section .note.GNU-stack,"",@progbits

As you could see it just accesses variable via %rip as you've done. Easy and simple. What about 32-bit version? Let's see:

$ toolchain/linux_x86_glibc/bin/i686-nacl-gcc -fPIC -fomit-frame-pointer -masm=intel -O3 -S -o- test.c 
.file "test.c"
.intel_syntax noprefix
.text
.align 32
.globl read_bar
.type read_bar, @function
read_bar:
add ecx, OFFSET FLAT:_GLOBAL_OFFSET_TABLE_
mov eax, DWORD PTR bar@GOTOFF[ecx]
pop ecx
nacljmp ecx
.size read_bar, .-read_bar
.align 32
.globl write_bar
.type write_bar, @function
write_bar:
add ecx, OFFSET FLAT:_GLOBAL_OFFSET_TABLE_
mov eax, DWORD PTR 4[esp]
mov DWORD PTR bar@GOTOFF[ecx], eax
pop ecx
nacljmp ecx
.size write_bar, .-write_bar
.local bar
.comm bar,4,4
.ident "GCC: (GNU) 4.4.3 20141209 (Native Client r14192, Git Commit 7faaabb9f10e6dcae5f2b799da43e236e65cda95)"
.section .text.__i686.get_pc_thunk.cx,"axG",@progbits,__i686.get_pc_thunk.cx,comdat
.p2align 5
.type __i686.get_pc_thunk.cx, @function
pop ecx
nacljmp ecx
.section .note.GNU-stack,"",@progbits

As you could see there are complex dance with $_GLOBAL_OFFSET_TABLE_ constant, helper function called "__i686.get_pc_thunk.cx" (it's in separate section to make sure all such helper functions will be combined into one large funtion by lnker) and so on. Far cry from simplicity of %rip use on x86-64, but, unfortunatelly, that's the best one could do. You could read about how all that dance works here: http://www.akkadia.org/drepper/dsohowto.pdf or you could just cargo-cult it into your program - but not try to be clever and rename function __i686.get_pc_thunk.cx or $_GLOBAL_OFFSET_TABLE_ constant!

P.S. All that dance is not NaCl invention - everything works with NaCl in exatly the same way it does without NaCl. NaCl only offence here is the fact that code is unmodifyable and thus non-PIC code will not work. PIC machinery itself is more-or-less the same with NaCl as without NaCl.

Message has been deleted
Message has been deleted

Joe Bruggeman

unread,
Jul 24, 2015, 3:45:19 PM7/24/15
to Native-Client-Discuss, kh...@chromium.org
Ok.

So I *should* be able to convert YASM PIC from

pand mm1, [somelabel]

to nacl gas

call __i686.get_pc_thunk.cx
add ecx
, OFFSET FLAT:
_GLOBAL_OFFSET_TABLE_
pand mm1
, bar@GOTOFF[ecx]

I don't need to wrap call with naclcall?

Victor Khimenko

unread,
Jul 24, 2015, 3:55:15 PM7/24/15
to Joe Bruggeman, Native-Client-Discuss
No. It's direct call, you don't need to (and in fact can't) sandbox target address for direct calls.

Just note that "call" is an expensive operation and add must immediately follow said call which means that usually these two instructions are only issued once and then the result is used as many times as needed (you don't need to keep it in %ecx, you can move it around). It'll be very slow to do that for every memory access.

Joe Bruggeman

unread,
Jul 24, 2015, 4:18:24 PM7/24/15
to Native-Client-Discuss, kh...@chromium.org
Ah, yes, I suppose I knew that already from all the 64 bit work i've done.

Works like a charm. Thanks for the help Khim.

One more question

If there was a register offset when referring to a symbol, eg

pand mm1, [bar + rax + 32]

Is this code correct?

call __i686.get_pc_thunk.cx
add ecx
, OFFSET FLAT:
_GLOBAL_OFFSET_TABLE_
lea ecx
, [ecx+rax+32]
pand mm1
, bar@GOTOFF[ecx]


Roland McGrath

unread,
Jul 24, 2015, 4:29:06 PM7/24/15
to native-cli...@googlegroups.com, Victor Khimenko
In x86-32 NaCl you can use the full range of addressing modes.
So there is no need for an intermediate LEA there.
You can just use "pand bar@GOTOFF+32(%ecx,%eax), %mm1" (I didn't put
it in Intel syntax because I'm not sure how it looks there).

Victor Khimenko

unread,
Jul 24, 2015, 4:58:54 PM7/24/15
to Roland McGrath, native-cli...@googlegroups.com
In intel syntax it'll be "pand mm1, bar@GOTOFF[ecx + eax + 32]" or "pand mm1, bar@GOTOFF + 32[ecx + eax]" or even "pand mm1, [bar@GOTOFF + 32 + ecx + eax]".

Note that situation with addressing here is somewhat simialr to NaCl x86-64 case: you are using %ecx here as a "poor man's %rip" substitute thus you could translate "[bar + rax + rax]" into "[bar@GOTOFF+ecx + eax + 32]", but if original used something like "[bar + rax + rbx] then you'll need lea, after all.
Reply all
Reply to author
Forward
0 new messages