Assembling 32-Bit Binaries on a 64-Bit System (Gnu Toolchain)

Linking assembly .s with gcc errors

Assemble with --32, and link with -m32.

gcc -m32 -o search search.s

Without -m32, the output is going to be a 64-bit executable. The error message is due to the input object being a 32-bit object; you can't produce a 64-bit executable from 32-bit objects.

Porting from 32 to 64-bit by just changing all the register names from eXX to rXX makes factorial return 0?

TL:DR: the factorial of the return address overflowed %rax, leaving 0, because you ported wrong.


Porting 32-bit code to 64-bit is not as simple as changing all the register names. That might get it to assemble, but as you found even this simple program behaves differently. In x86-64, push %reg and call both push 64-bit values, and modify rsp by 8. You would see this if you single-stepped your code with a debugger. (See the bottom of the x86 tag wiki for info using gdb for asm.)

You're following a book that uses 32-bit examples, so you should probably just build them as 32-bit executables instead of trying to port them to 64-bit before you know how.


Your sys_exit() using the 32-bit int 0x80 ABI still works (What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code?), but you will run into trouble with system calls if you try to pass 64-bit pointers. Use the 64-bit ABI.

You will also run into problems if you want to call any library functions, because the standard function-calling convention is different, too. See Why parameters stored in registers and not on the stack in x86-64 Assembly?, and the 64-bit ABI link, and other calling-convention docs in the x86 tag wiki.


But you're not doing any of that, so the problem with your program simply comes down to not accounting for the doubled "stack width" in x86-64. Your factorial function reads the return address as its argument.

Here's your code, commented to explain what it actually does

push $4                    # rsp-=8.  (rsp) = qword 4
# non-standard calling convention with args on the stack.
call factorial # rsp-=8. (rsp) = return address. RIP=factorial
add $4, %rsp # misalign the stack, so it's pointing to the top half of the 4 you pushed earlier.
# if this was in a function that wanted to return, you'd be screwed.

mov %rax, %rbx # copy return value to first arg of system call
mov $1, %rax #eax = __NR_EXIT from asm/unistd_32.h, wasting 2 bytes vs. mov $1, %eax
int $0x80 # 32-bit ABI system call, eax=call number, ebx=first arg. sys_exit(factorial(4))

So the caller is sort of fine (for the non-standard 64-bit calling convention you've invented that passes all args on the stack). You might as well omit the add to %rsp entirely, since you're about to exit without touching the stack any further.

.type factorial, @function #defines the code below as being a function

factorial: #function label
push %rbp #rsp-=8, (rsp) = rbp
mov %rsp, %rbp # make a traditional stack frame

mov $1, %rax #retval = 1. (Wasting 2 bytes vs. the exactly equivalent mov $1, %eax)

mov 8(%rbp), %rcx #load the return address into %rcx

... and calculate the factorial

For static executables (and dynamically linked executables that aren't ASLR enabled with PIE), _start is normally at 0x4000c0. Your program will still run nearly instantaneously on a modern CPU, because 0x4000c0 * 3c latency of imul is still only 12.5 million core clock cycles. On a 4GHz CPU, that's 3 milliseconds of CPU time.

If you'd made a position-independent executable by linking with gcc foo.o on a recent distro, _start would have an address like 0x5555555545a0, and your function would have taken ~70368 seconds to run on a 4GHz CPU with 3-cycle imul latency.

4194496! includes many even numbers, so its binary representation has many trailing zeros. The whole %rax will be zero by the time you're done multiplying by every number from 0x4000c0 down to 1.

The exit status of a Linux process is only the low 8 bits of the integer you pass to sys_exit() (because the wstatus is only a 32-bit int and includes other stuff, like what signal ended the process. See wait4(2)). So even with small args, it doesn't take much.

64-bit registers under 32-bit windows

  1. Yes, a 64-bit processor has 3 size modes, 16-bit, 32-bit and 64-bit. An operating system cannot run code in a mode larger than the operating system itself. Thus Vista 32-bit can only run code built for 16-bits and 32-bits. Your processor does indeed support 64-bits, so you should try to install a 64-bit OS on it. If your computer came with actual windows Vista CDs, there may be a separate CD for Vista 64. If you build a new toolchain with x86-64 support you will be able to compile code that uses 64-bit instructions and registers, but you will not be able to run the resulting executables unless you install a 64-bit OS.

  2. Since rax is a 64-bit register (the 32-bit equivalent is eax) it cannot be accessed except from a 64-bit program. The reason for this is a rather convoluted explanation of how the processor interprets the commands your program sends.

But, you don't actually need access to 64-bit code in order to use MMX, SSE, SSE2, and SSE3. All of those instructions are available in 32-bit mode and are supported by your processor.

You also might want to consider running a Virtual Machine and putting Ubuntu inside it. This will allow you to run Linux inside a window on your Windows desktop. I think you will have a easier time of things using the GNU tool chain (gcc, etc) natively than you will using mingw32. VMs are easy to install and use in this day and age, there's little reason to use mingw32 anymore. You could also look in to MS Visual C++ Express, which a windows-based compiler that is free from Microsoft, but it doesn't support 64-bit (you have to pay for a version that does).

Exception: STATUS_ACCESS_VIOLATION at rip=0010040108D when executing program

OK, I've managed to figure this out

I've finally successfully executed the program by compiling at my cygwin64 that is linked to my ECLIPSE

And I've used two simple commands:

as function.asm -o function.o
g++ main.cpp function.o

And the AT&T assembly syntax for the external function:

.code32 

.global array

.section .text

array: pushl %ebp
movl %esp, %ebp
pushl %ecx
pushl %esi

movl 12(%ebp), %ecx
movl 8(%ebp), %esi

xorl %eax, %eax

lp: addl (%esi), %eax
addl $4, %esi
loop lp

popl %esi
popl %ecx
popl %ebp
ret

For some reason the compiler requires me to add a carriage return to the end of the assembler file (function.asm)

So now, to launch my 32-bit source: I just probably need to hook up the above commands with my ECLIPSE



Related Topics



Leave a reply



Submit