flat assembler
Message board for the users of flat assembler.
Index
> Linux > fasm as 64-bit ELF executable Goto page 1, 2 Next |
Author |
|
Tomasz Grysztar 07 Dec 2016, 19:49
While 64-bit systems are in general capable of running a 32-bit executables, there are some Linux builds out there that have this feature disabled in the kernel. This makes it impossible to run fasm on such machines without writing a 64-bit version from scratch. Or does it?
I've been thinking about this for a few years already, and I had some ideas on how to run fasm's core in "use64" setting. Now I finally decided to confront my ideas with reality. The trick lies in the fact that 32-bit addressing is still available in long mode. As long as the address fits in 32 bits, [ebx] can be used instead of [rbx] and the instruction is going to be correctly encoded and executed. Therefore it should suffice to ensure that all memory allocated for fasm's use lies in the low 4G of addressing space. And fortunately sys_brk, which fasm's Linux interface uses to pre-allocate all the memory that fasm is going to use, appears to do exactly that - it grows the program segment (which we can put at a low address) so the allocated memory should stay in the low addressing. But there is another problem - there is no 32-bit PUSH/POP in long mode. These two instructions need to be replaced with macros that emulate 32-bit stack. For example "push eax" has to become: Code: lea rsp,[rsp-4] mov [rsp],eax Similarly, the JMP/CALL instructions with target specified by 32-bit variable need to be emulated. And there are also two special cases, "call directive_handler" and "call define_data", where fasm does not use RET but treats these instructions as a kind of PUSH to store 32-bit address of a procedure on the stack. These two cases need to be recognized by macros, though I'm considering cleaning up fasm's code to get rid of these tricks - they are not really needed there, and they add another unnecessary layer of confusion. Lastly, fasm uses a couple of instructions that are not available in long mode - SALC and JCXZ. Again, emulating them with macros is possible. I checked that fasm does not rely on them preserving flags, so the macros can be kept simple. This led me to the creation of the following set of macros that allow to assemble fasm's core as a long-mode-compatible code: Code: esp equ +rsp macro pushD arg { lea rsp,[rsp-4] if arg eqtype eax mov dword [rsp],arg else mov r8d,dword arg mov [rsp],r8d end if } macro popD arg { if arg eqtype [mem] mov r8d,[rsp] mov dword arg,r8d else mov arg,dword [rsp] end if lea rsp,[rsp+4] } macro add dest,src { if dest eq esp add rsp,src else add dest,src end if } macro mov dest,src { if src eq esp mov dest,ESP else mov dest,src end if } macro cmp dest,src { if dest eq esp cmp ESP,src else cmp dest,src end if } macro use32 { macro push args \{ define arg@push irps sym, args \\{ define status@push match =dword, sym \\\{ define status@push 1 \\\} match [any, status@push arg@push sym \\\{ define arg@push [any match [mem], arg@push \\\\{ pushD [mem] define arg@push \\\\} define status@push 1 \\\} match [, status@push arg@push sym \\\{ define arg@push [ define status@push 1 \\\} match , status@push \\\{ pushD sym \\\} \\} \} macro pop args \{ define arg@pop irps sym, args \\{ define status@pop match =dword, sym \\\{ define status@pop 1 \\\} match [any, arg@pop sym \\\{ define arg@pop [any match [mem], arg@pop \\\\{ popD [mem] define arg@pop \\\\} define status@pop 1 \\\} match [, status@pop arg@pop sym \\\{ define arg@pop [ define status@pop 1 \\\} match , status@pop \\\{ popD sym \\\} \\} \} macro jmp arg \{ if arg eq near eax jmp near rax else if arg eq near edx jmp near rdx else if arg eqtype [mem] mov r8d,arg jmp near r8 else jmp arg end if \} macro call arg \{ if arg eq define_data | arg eq directive_handler ; special cases that do not use RET \local next pushD next if arg <> $ jmp arg end if next: else if 1 match =near =dword [mem], arg \\{ mov r8d,[mem] call near r8 else \\} call arg end if \} macro salc ; for fasm's core it does not need to preserve flags \{ setc al neg al \} macro jcxz target ; for fasm's core it does not need to preserve flags \{ test cx,cx jz target \} use64 } macro use16 { purge push,pop,jmp,call,salc,jcxz use16 } use32 Of course, the interface code needed to be adapted to x64 environment, too. Since the command line parameters may reside in the high memory, the ones that are used by the core have to be copied into a low memory area. I also thought that syscalls may need additional code to align the stack (because 32-bit PUSH/POP emulation causes it to often be terribly misaligned for a 64-bit world standards), but apparently it works correctly without it (though it definitely would be required in 64-bit Windows). I'm attaching the additional source files (that need to be merged into fasm's SOURCE directory from the regular downloads) and the assembled 64-bit ELF executable. It seems to work correctly in my tests, but please let me know if you find any instability.
Last edited by Tomasz Grysztar on 08 Dec 2016, 19:24; edited 1 time in total |
|||||||||||
07 Dec 2016, 19:49 |
|
redsock 07 Dec 2016, 21:56
This is very cool, thanks for your painful effort! I have run non-multiarch platforms before, and in some cases they make for very nice minimalist environments.
Are there any standard distros that are not multilib? |
|||
07 Dec 2016, 21:56 |
|
Tomasz Grysztar 07 Dec 2016, 22:08
redsock wrote: Are there any standard distros that are not multilib? |
|||
07 Dec 2016, 22:08 |
|
Tomasz Grysztar 08 Dec 2016, 08:03
I have updated the attachment with an improved version, I rewrote the 32-bit stack emulation in such way that PUSH/POP with multiple arguments in single line generates just one LEA instruction:
Code: esp equ +rsp macro pushD [arg] { common local offset,total offset = 0 lea rsp,[rsp-total] forward offset = offset + 4 if arg eqtype eax mov dword [rsp+total-offset],arg else mov r8d,dword arg mov [rsp+total-offset],r8d end if common total = offset } macro popD [arg] { common local offset offset = 0 forward if arg eqtype [mem] mov r8d,[rsp+offset] mov dword arg,r8d else mov arg,dword [rsp+offset] end if offset = offset + 4 common lea rsp,[rsp+offset] } macro add dest,src { if dest eq esp add rsp,src else add dest,src end if } macro mov dest,src { if src eq esp mov dest,ESP else mov dest,src end if } macro cmp dest,src { if dest eq esp cmp ESP,src else cmp dest,src end if } macro use32 { macro push args \{ local list,arg,status define list define arg irps sym, args \\{ define status match =dword, sym \\\{ define status : \\\} match [any, status arg sym \\\{ define arg [any match [mem], arg \\\\{ match previous, list \\\\\{ define list previous,[mem] \\\\\} match , list \\\\\{ define list [mem] \\\\\} define arg \\\\} define status : \\\} match [, status arg sym \\\{ define arg [ define status : \\\} match , status \\\{ match previous, list \\\\{ define list previous,sym \\\\} match , list \\\\{ define list sym \\\\} \\\} \\} match arg, list \\{ pushD arg \\} \} macro pop args \{ local list,arg,status define list define arg irps sym, args \\{ define status match =dword, sym \\\{ define status : \\\} match [any, status arg sym \\\{ define arg [any match [mem], arg \\\\{ match previous, list \\\\\{ define list previous,[mem] \\\\\} match , list \\\\\{ define list [mem] \\\\\} define arg \\\\} define status : \\\} match [, status arg sym \\\{ define arg [ define status : \\\} match , status \\\{ match previous, list \\\\{ define list previous,sym \\\\} match , list \\\\{ define list sym \\\\} \\\} \\} match arg, list \\{ popD arg \\} \} macro jmp arg \{ if arg eq near eax jmp near rax else if arg eq near edx jmp near rdx else if arg eqtype [mem] mov r8d,arg jmp near r8 else jmp arg end if \} macro call arg \{ if arg eq define_data | arg eq directive_handler ; special cases that do not use RET \local next pushD next if arg <> $ jmp arg end if next: else if 1 match =near =dword [mem], arg \\{ mov r8d,[mem] call near r8 else \\} call arg end if \} macro salc ; for fasm's core it does not need to preserve flags \{ setc al neg al \} macro jcxz target ; for fasm's core it does not need to preserve flags \{ test cx,cx jz target \} use64 } macro use16 { purge push,pop,jmp,call,salc,jcxz use16 } use32 |
|||
08 Dec 2016, 08:03 |
|
revolution 08 Dec 2016, 08:55
Tomasz Grysztar wrote: I also thought that syscalls may need additional code to align the stack (because 32-bit PUSH/POP emulation causes it to often be terribly misaligned for a 64-bit world standards), but apparently it works correctly without it (though it definitely would be required in 64-bit Windows). As for not writing the stack alignment code because it currently works on Linux: A quick search of the Linux specs shows "The stack pointer shall maintain 8-byte alignment", so it appears as though this will be no problem. Unless, that is, Linux decides to break everything with the next release. Last edited by revolution on 08 Dec 2016, 16:09; edited 1 time in total |
|||
08 Dec 2016, 08:55 |
|
Tomasz Grysztar 08 Dec 2016, 09:03
revolution wrote: I have never seen any version of 64-bit Windows that can't run 32-bit code, so perhaps a Windows version is not very important. And with the enormous amount of 32-bit programs still around it would be unbelievable that support would ever be dropped. The only possible use case would be for files larger then ~3GB but that would also require rewriting all the addresses everywhere, so not really practical that I can see. revolution wrote: As for not writing the stack alignment code because it currently works on Linux: A quick search of the Linux specs shows "The stack pointer shall maintain 8-byte alignment", so it appears as though this will be no problem. Unless that is Linux decides to break everything with the next release. |
|||
08 Dec 2016, 09:03 |
|
revolution 08 Dec 2016, 09:09
Tomasz Grysztar wrote: The problem is that fasm's emulation of 32-bit stack sometimes leaves it aligned on 4-byte but not 8-byte boundary. But even this seems to work with syscalls. But even so, stack alignment requirements do not affect internal calls within an application. Only API calls need to be aligned, so it would appear that just fasm.asm and system.inc would need to have the stack adjustment code. |
|||
08 Dec 2016, 09:09 |
|
Tomasz Grysztar 08 Dec 2016, 16:28
Does anyone here run 64-bit OpenBSD somewhere? I wonder if this would work there.
|
|||
08 Dec 2016, 16:28 |
|
fasmnewbie 08 Dec 2016, 16:33
Will this in any way affect anything in elf.inc and import64.inc? These changes kind of scare me because I am relying on both for my SO.
|
|||
08 Dec 2016, 16:33 |
|
revolution 08 Dec 2016, 16:44
fasmnewbie wrote: Will this in any way affect anything in elf.inc and import64.inc? |
|||
08 Dec 2016, 16:44 |
|
rugxulo 08 Dec 2016, 19:13
revolution wrote: I have never seen any version of 64-bit Windows that can't run 32-bit code, so perhaps a Windows version is not very important. Quoting here: Microsoft wrote:
Probably somewhat rare, but who knows. Señor Naïveté wrote:
If decades of DOS software has been thrown away, why not also Win32? |
|||
08 Dec 2016, 19:13 |
|
Tomasz Grysztar 08 Dec 2016, 20:30
I have cleaned up some things in fasm's core to make this adaptation a bit simpler and I included this 64-bit executable in the Linux package with the new 1.71.58 release. If I only made it available here, it is probable that the people that may find it useful would never find out about it.
|
|||
08 Dec 2016, 20:30 |
|
revolution 09 Dec 2016, 02:47
rugxulo wrote:
rugxulo wrote:
rugxulo wrote: If decades of DOS software has been thrown away, why not also Win32? |
|||
09 Dec 2016, 02:47 |
|
system error 09 Dec 2016, 10:35
interesting... Tomasz, how about that nasty Y2038 issue? Limiting syscall arguments to 32-bit may re-introduce that problem in 64-bit environment. timespec is 64-bit the last time I checked.
|
|||
09 Dec 2016, 10:35 |
|
Tomasz Grysztar 09 Dec 2016, 11:19
system error wrote: interesting... Tomasz, how about that nasty Y2038 issue? Limiting syscall arguments to 32-bit may re-introduce that problem in 64-bit environment. timespec is 64-bit the last time I checked. |
|||
09 Dec 2016, 11:19 |
|
JohnFound 09 Dec 2016, 11:42
I missed this thread from the beginning, but it is remarkable. Very interesting workaround.
|
|||
09 Dec 2016, 11:42 |
|
CandyMan 08 Feb 2017, 21:40
Tomasz whether it is possible to porting fasm to windows 64-bit (like you did for linux) ? Below is such a code correct?
Code: close: call PushAll ;\ mov rbp,rsp ; > #1 and rsp,not 0xF ;/ invoke CloseHandle,rbx mov rsp,rbp ;\ #1 call PopAll ;/ ret #1 put this code in every invoked procedure through fasm PushAll: xchg r15,[rsp] ;rip push r14 r13 r12 r11 r10 r9 r8 rbp rsi rdi rdx rcx rbx rax push r15 mov r15,[rsp+15*8] ret PopAll: pop r15 ;rip pop rax rbx rcx rdx rdi rsi rbp r8 r9 r10 r11 r12 r13 r14 xchg [rsp],r15 ret _________________ smaller is better |
|||
08 Feb 2017, 21:40 |
|
revolution 09 Feb 2017, 04:41
comrade wrote: Don't you need to keep the stack aligned to 16-bytes on 64-bit? It is true for Win64 ABI, not sure if likewise for Linux64. Edit: The original message has disappeared. Oh well. |
|||
09 Feb 2017, 04:41 |
|
YONG 09 Jul 2017, 12:26
@T.G.: The 64-bit compiler may have some memory management issue. Refer to:
https://board.flatassembler.net/topic.php?p=197856#197856 BTW, where can I find a full list of fasm reserved names for the 64-bit compiler? |
|||
09 Jul 2017, 12:26 |
|
Goto page 1, 2 Next < Last Thread | Next Thread > |
Forum Rules:
|
Copyright © 1999-2024, Tomasz Grysztar. Also on GitHub, YouTube.
Website powered by rwasa.