NASM - The Netwide Assembler
NASM Forum => Programming with NASM => Topic started by: ggnasm800 on December 28, 2020, 08:41:49 PM
-
I am trying to build raw executable binary to load and execute at arbitrary memory address at boot.
For elf binary, i can easily generate 32-bit binary:
nasm -felf32 -F dwarf boota.asm
gcc -m32 bootc.c boota.o -o boot.elf.
However i dont need elf file along its headers. I also see when main() is declared (as program entry) compiler puts additional (possibly O/S dependent code) prior to executing main() so I dont need those too.
I sort of did hacked the resulting elf binary such that bootloader code will directly jump into main() but when main() calls one of the assembler function, it interprets call dest. address 2 bytes less.
How do i make it work?
in brief:
opcode on vm sees: e8 48 00 (call label at 6a) (using virtualbox vdebug capability to step-through)
opcode on objdump sees: (call label at 6c) e8 48 00 00 00
more code (code starts executing at 800:00 address after bios boot):
from virtual box debugger:
VBoxDbg> u 800:0
0800:0 8d 4c 24 lea cx, [si+024h]
0800:03 04 83 add AL, 083h
0800:05 e4 f0 in AL, 0f0h
0800:07 ff 71 fc push word [bx+di-004h]
0800:0a 55 push bp
0800:0b 89 e5 mov bp, sp
0800:0d 53 push bx
0800:0e 51 push cx
0800:0f e8 ef fe call 0ff01h
0800:00000012 ff ff Illegal opcode
0800:00000014 81 c3 db 1a add bx, 01adbh
0800:00000018 00 00 add byte [bx+si], al
0800:0000001a 83 ec 0c sub sp, byte 0000ch
0800:0000001d 6a 5b push byte 0005bh
0800:0000001f e8 48 00 call 0006ah <--------------------------------- ( here it is calling the assembler defined function or label, but as you can see below, function entry is at 6c)
0800:00000022 00 00 add byte [bx+si], al
0800:00000024 83 c4 10 add sp, byte 00010h
0800:00000027 83 ec 04 sub sp, byte 00004h
0800:0000002a 6a f9 push byte 0fff9h
0800:0000002c 6a fc push byte 0fffch
0800:00000065 66 39 d0 cmp eax, edx
0800:00000068 66 0f 4c c2 cmovl eax, edx
0800:0000006c b4 0e mov AH, 00eh <---------- (but function is at 6c)
0800:0000006e b0 25 mov AL, 025h
0800:00000070 cd 10
disassemly looks OK:
OBJDUMP OUTPUT:
Disassembly of section .data:
0 <.data>:
0: 8d 4c 24 04 lea 0x4(%esp),%ecx
4: 83 e4 f0 and $0xf0,%esp
7: ff 71 fc pushl -0x4(%ecx)
a: 55 push %ebp
b: 89 e5 mov %esp,%ebp
d: 53 push %ebx
e: 51 push %ecx
f: e8 ef fe ff ff call 0xffffff03
14: 81 c3 db 1a 00 00 add $0x1adb,%ebx
1a: 83 ec 0c sub $0xc,%esp
1d: 6a 5b push $0x5b
1f: e8 48 00 00 00 call 0x6c
-
Hi ggnasm800,
Welcome to the forum.
I don't think I can help you much with this.
Telling GCC "--nostartfiles" will get rid pf the startup code that calls main.
Your virtual box debugger is trying to disassemble 16 bit code but it is 32 bit.
The int 10h is a bios interrupt - 16 bit code. We don't see if you have put the CPU into 32 bit mode or not. If so, the int 10h isn't going to work. If not, the rest of it isn't going to work.
You might do better to ask in an OS development group.
Best,
Frank
-
Here's a possible example of how to do it: Let's say you have a 16 bits code and want to mix it with C code compiled with GCC. In this example I use 2 different text sections: One for my assembly code, one for C code. Here's the linker script:
/* linker.ld */
OUTPUT_FORMAT(binary)
OUTPUT_ARCH(i386)
SECTIONS {
.text16 : { *(.text16) }
.text : { *(.text) } /* GCC's C code */
.data : { *(.data) }
.rodata : { *(.rodata) }
.bss : {
__begin_bss = .;
*(.bss)
__end_bss = .;
}
}
And my assembly code could be something like this:
; test.asm
;
bits 16
; Symbols defined elsewhere...
extern __begin_bss
extern __end_bss
extern f
; Our 16 bits text section.
section .text16
; Starting here.
; Default if up.
cld
; Setup selectors (don't need to setup ss).
push cs
pop ax
mov ds,ax
mov es,ax
; Clear BSS section.
lea edi,[__begin_bss]
lea ecx,[__end_bss]
sub ecx,edi
jz .skip
xor eax,eax
rep stosb
.skip:
call dword f ; call C f() function.
; dword is necessary here because
; gcc is a 32 bits compiler.
; halt
.halt:
hlt
jmp .halt
section .text
; BIOS tty write routine. Used by C code:
; __attribute__((fastcall)) void putc( char c );
;
; Entry: cl = char to write.
global putc
putc:
push bx ; By SysV calling convention.
mov ah,0x0e
mov al,cl
mov bx,7 ; Attrib white on black, page 0.
int 0x10
pop bx ; By SysV callign convention.
retd ; 32 bits return.
And let's say by function f() is defined as:
/* func.c */
extern void putc( char ) __attribute__((fastcall));
void f( void )
{
static const char msg[] = "Hello!\r\n";
const char *p;
p = msg;
while ( *p )
{
putc( *p );
p++;
}
}
You must be careful compiling the GCC code because you can't use any standard library functions and your code cannot be a PIE (Position Independent Executable). So, the makefile for this example would be:
# Makefile
CFLAGS=-O2 -m16 -ffreestanding -nostdlib -fcf-protection=none -fno-pie
test: test.o func.o
ld -T linker.ld -o $@ $^
test.o: test.asm
nasm -felf32 -o $@ $<
func.o: func.c
Notice the linker will create the "binary" file based on elf32 object file formats.
PS: Even if using -m16 option, GCC's code is still 32 bits code. You must be careful using "far" pointers (not available) and load the appropriate selector (DS) by yourself, maybe using inline assembly.
[]s
Fred