-1

Ok so I have two files one is A C file and the other one is A ASM file. And their code is.

C file

void print()
{
print_char('A');
}

ASM file

print_char:
push ebp ; prolouge
mov ebp, esp
mov ah, 0eh ; set code for printing
mov al, [esp+8] ; move char into al
int 10h ; call int 10h
mov esp, ebp ; epilouge
pop ebp
ret

And it prints something called A triple bar. Doe's anyone know why

Michael Petch
  • 46,082
  • 8
  • 107
  • 198
8_BIT
  • 29
  • 1
  • 5
  • 3
    You forgot to specify what environment. It's very suspicious that you apparently have a working `int 10h` but use 32 bit code. The former usually implies 16 bit code. Anyway, examine the generated code for the C function and/or use a debugger to see stack contents. – Jester Aug 23 '20 at 22:55
  • 1
    What operating system are you programming for? – fuz Aug 23 '20 at 22:57
  • I probably should have added this before but I'm doing this in qemu on unbuntu 20.4 – 8_BIT Aug 23 '20 at 23:06
  • 2
    With what compiler? In 16-bit mode, the return address will only be 16 bits wide for a near `call`. I'm not sure exactly what `gcc -m16` does about that, if it just lets the stack get misaligned. But look at compiler output for a C function that reads an arg. Also, if you're going to reference args relative to ESP instead of EBP, it's a waste of instructions to set up EBP as a frame pointer. – Peter Cordes Aug 23 '20 at 23:11
  • nasm and gcc you are right about wasted instructions so I'll fix that – 8_BIT Aug 23 '20 at 23:16
  • 1
    Is your assembly code with `print_char` using `bits 32` or `bit 16` for that code? And are you using `-m16` when compiling with gcc? – Michael Petch Aug 23 '20 at 23:35
  • 5
    It would help if you showed complete code example for your bootloader, your kernel and assembly language code and the commands you use to compile/assemble/link your code (and generate the disk image). If you use a linker script that we'd need to see as well. – Michael Petch Aug 23 '20 at 23:45
  • here is the full bootloader https://pastebin.com/2iXWU1Zy – 8_BIT Aug 24 '20 at 00:10
  • 2
    That is 16 bit boot sector code, it's unclear how you fit the C part into that. Anyway, as we already told you, you should probably write 16 bit asm, e.g. `push bp; mov bp, sp; mov ah, 0eh; mov al, [bp+4]; int 10h; pop bp; ret`. – Jester Aug 24 '20 at 00:57
  • it now outputs something but its A smiley face type thing – 8_BIT Aug 24 '20 at 01:52
  • Also the code you pasted doesn't bother setting up a known good environment, most importantly the segments and the stack pointer. – Jester Aug 24 '20 at 01:58
  • for asm I use nasm and gcc for c code – 8_BIT Aug 25 '20 at 04:18
  • I have provided an answer that shows how a function like your `print_char` can be made to work in a proper environment. My answer also demonstrates a [mcve]. It is a rather minimal example of all the code and instructions to compile/assemble/link to demonstrate code. I discuss a number of issues that may be relevant to your problem but without you providing complete code it is a guessing game. If you were to alter your question and make a minimal complete example that demonstrates your issue you may be able to get a more specific answer to your problem. – Michael Petch Aug 25 '20 at 14:00

1 Answers1

1

I have a Stackoverflow answer with many bootloader tips including setting up segments, the stack, and not relying on registers to have particular values etc. I have also have a Stackoverflow answer that is a simple bootloader that can load a kernel (or second stage) from a 1.44MB floppy image.

If Int 10h/ah=0eh is printing characters and not faulting then the code you are trying to generate appears to be intended to run in real mode. You mention you are using Ubuntu don't mention if you are using a cross compiler (I highly recommend using one). The other possibility is that you are using the experimental ia16-elf-gcc compiler that can generate 16-bit real mode code that can be run on a variety of processors from the 8086 to the 80386+.

I will assume in the absence of any additional information that you are using your native gcc compiler on Ubuntu and not a cross compiler or the ia16-gcc compiler. You should be aware that a native GCC can generate code that runs in real mode using the -m16 option but the generated code will only run on an 80386+ or better processor. Unless you go to great lengths the code generated by GCC should be placed in the first 64KiB of memory and I recommend CS=DS=ES=SS=0 for the fewest hassles. Mainstream GCC has no understanding of real mode segment:offset addressing.

You don't really show enough of your code to know what all the problems you are encountering. One that stands out to me is in the print_char:

print_char:
push ebp ; prolouge
mov ebp, esp
mov ah, 0eh ; set code for printing
mov al, [esp+8] ; move char into al
int 10h ; call int 10h
mov esp, ebp ; epilouge
pop ebp
ret

In particular the ret will cause problems. Because GCC is generating code with 32-bit operand and 32-bit address overrides it expects that the calling function will push a 32-bit return address on the stack. In real mode a simple ret in your assembly code will return to the 16-bit address on the stack and only pop 2 bytes off rather than 4. This will cause a stack imbalance when it returns to the caller and this can lead to unpredictable results. To fix this you need to use a 32-bit operand override on the ret, so it has to be o32 ret.

If you are calling a C function from assembly you have to ensure that you use a version of the CALL that pushes a full 32-bit address on the stack rather than a 16-bit one. Failure to do so will cause any parameters passed on the stack to be incorrectly indexed and GCC will do a ret that will expect a 32-bit return address to be popped off the stack. If you had a function called kmain (kernel entry point as an example) then you might think to encode the CALL this way:

call kmain

What you really need is:

call dword kmain

Other issues you may have to look at:

  • Ensure the segment registers are set correctly before calling into your C code
  • Set a proper stack pointer SS:SP
  • Set the DF flag to 0 (Using CLD)
  • If using floating point x87 floating point use finit to initialize it
  • Make sure the stack is aligned on a 16 byte boundary before calling any C from assembly.
  • Ensure your entire kernel is loaded in memory
  • You need to zero the BSS section out before the C code utilizes any uninitialized or zero initialized global variables
  • Ensure the code generated has the appropriate VMA/Origin point.
  • Ensure C code is not built using Position Independent Code (PIC) using -fno-pic
  • Ensure the C code is built with the -m16 option so that the instructions produced will work while running in 16-bit real mode on an 80386+.
  • Remove asynchronous unwind tables with -fno-asynchronous-unwind-tables and turn off exceptions with -fno-exceptions

As an example I'm going to use my bootloader to load a kernel at 0x7e00. The calls print_char and a simple print_string.

boot.asm:

STAGE2_ABS_ADDR  equ 0x07e00
STAGE2_RUN_SEG   equ 0x0000
STAGE2_RUN_OFS   equ STAGE2_ABS_ADDR
                                ; Run stage2 with segment of 0x0000 and offset of 0x7e00

STAGE2_LOAD_SEG  equ STAGE2_ABS_ADDR>>4
                                ; Segment to start reading Stage2 into
                                ;     right after bootloader

STAGE2_LBA_START equ 1          ; Logical Block Address(LBA) Stage2 starts on
                                ;     LBA 1 = sector after boot sector
STAGE2_LBA_END   equ STAGE2_LBA_START + NUM_STAGE2_SECTORS
                                ; Logical Block Address(LBA) Stage2 ends at
DISK_RETRIES     equ 3          ; Number of times to retry on disk error

bits 16
ORG 0x7c00

; Include a BPB (1.44MB floppy with FAT12) to be more compatible with USB floppy media
%ifdef WITH_BPB
%include "bpb.inc"
%endif

boot_start:
    xor ax, ax                  ; DS=SS=0 for stage2 loading
    mov ds, ax
    mov ss, ax                  ; Stack at 0x0000:0x7c00
    mov sp, 0x7c00
    cld                         ; Set string instructions to use forward movement

    ; Read Stage2 1 sector at a time until stage2 is completely loaded
load_stage2:
    mov [bootDevice], dl        ; Save boot drive
    mov di, STAGE2_LOAD_SEG     ; DI = Current segment to read into
    mov si, STAGE2_LBA_START    ; SI = LBA that stage2 starts at
    jmp .chk_for_last_lba       ; Check to see if we are last sector in stage2

.read_sector_loop:
    mov bp, DISK_RETRIES        ; Set disk retry count

    call lba_to_chs             ; Convert current LBA to CHS
    mov es, di                  ; Set ES to current segment number to read into
    xor bx, bx                  ; Offset zero in segment

.retry:
    mov ax, 0x0201              ; Call function 0x02 of int 13h (read sectors)
                                ;     AL = 1 = Sectors to read
    int 0x13                    ; BIOS Disk interrupt call
    jc .disk_error              ; If CF set then disk error

.success:
    add di, 512>>4              ; Advance to next 512 byte segment (0x20*16=512)
    inc si                      ; Next LBA

.chk_for_last_lba:
    cmp si, STAGE2_LBA_END      ; Have we reached the last stage2 sector?
    jl .read_sector_loop        ;     If we haven't then read next sector

.stage2_loaded:
    mov ax, STAGE2_RUN_SEG      ; Set up the segments appropriate for Stage2 to run
    mov ds, ax
    mov es, ax

    ; FAR JMP to the Stage2 entry point at physical address 0x07e00
    xor ax, ax                  ; ES=FS=GS=0 (DS zeroed earlier)
    mov es, ax
    mov fs, ax
    mov gs, ax
    ; SS:SP is already at 0x0000:0x7c00, keep it that way
    ; DL still contains the boot drive number
    ; Far jump to second stage at 0x0000:0x7e00
    jmp STAGE2_RUN_SEG:STAGE2_RUN_OFS

.disk_error:
    xor ah, ah                  ; Int13h/AH=0 is drive reset
    int 0x13
    dec bp                      ; Decrease retry count
    jge .retry                  ; If retry count not exceeded then try again

error_end:
    ; Unrecoverable error; print drive error; enter infinite loop
    mov si, diskErrorMsg        ; Display disk error message
    call print_string
    cli
.error_loop:
    hlt
    jmp .error_loop

; Function: print_string
;           Display a string to the console on display page 0
;
; Inputs:   SI = Offset of address to print
; Clobbers: AX, BX, SI

print_string:
    mov ah, 0x0e                ; BIOS tty Print
    xor bx, bx                  ; Set display page to 0 (BL)
    jmp .getch
.repeat:
    int 0x10                    ; print character
.getch:
    lodsb                       ; Get character from string
    test al,al                  ; Have we reached end of string?
    jnz .repeat                 ;     if not process next character
.end:
    ret

;    Function: lba_to_chs
; Description: Translate Logical block address to CHS (Cylinder, Head, Sector).
;
;   Resources: http://www.ctyme.com/intr/rb-0607.htm
;              https://en.wikipedia.org/wiki/Logical_block_addressing#CHS_conversion
;              https://stackoverflow.com/q/45434899/3857942
;              Sector    = (LBA mod SPT) + 1
;              Head      = (LBA / SPT) mod HEADS
;              Cylinder  = (LBA / SPT) / HEADS
;
;      Inputs: SI = LBA
;     Outputs: DL = Boot Drive Number
;              DH = Head
;              CH = Cylinder (lower 8 bits of 10-bit cylinder)
;              CL = Sector/Cylinder
;                   Upper 2 bits of 10-bit Cylinders in upper 2 bits of CL
;                   Sector in lower 6 bits of CL
;
;       Notes: Output registers match expectation of Int 13h/AH=2 inputs
;
lba_to_chs:
    push ax                    ; Preserve AX
    mov ax, si                 ; Copy LBA to AX
    xor dx, dx                 ; Upper 16-bit of 32-bit value set to 0 for DIV
    div word [sectorsPerTrack] ; 32-bit by 16-bit DIV : LBA / SPT
    mov cl, dl                 ; CL = S = LBA mod SPT
    inc cl                     ; CL = S = (LBA mod SPT) + 1
    xor dx, dx                 ; Upper 16-bit of 32-bit value set to 0 for DIV
    div word [numHeads]        ; 32-bit by 16-bit DIV : (LBA / SPT) / HEADS
    mov dh, dl                 ; DH = H = (LBA / SPT) mod HEADS
    mov dl, [bootDevice]       ; boot device, not necessary to set but convenient
    mov ch, al                 ; CH = C(lower 8 bits) = (LBA / SPT) / HEADS
    shl ah, 6                  ; Store upper 2 bits of 10-bit Cylinder into
    or  cl, ah                 ;     upper 2 bits of Sector (CL)
    pop ax                     ; Restore scratch registers
    ret

; If not using a BPB (via bpb.inc) provide default Heads and SPT values
%ifndef WITH_BPB
numHeads:        dw 2          ; 1.44MB Floppy has 2 heads & 18 sector per track
sectorsPerTrack: dw 18
%endif

bootDevice:      db 0x00
diskErrorMsg:    db "Unrecoverable disk error!", 0

; Pad boot sector to 510 bytes and add 2 byte boot signature for 512 total bytes
TIMES 510-($-$$) db  0
dw 0xaa55

; Beginning of stage2. This is at 0x7E00 and will allow your stage2 to be 32.5KiB
; before running into problems. DL will be set to the drive number originally
; passed to us by the BIOS.

NUM_STAGE2_SECTORS equ (stage2_end-stage2_start+511) / 512
                                ; Number of 512 byte sectors stage2 uses.

stage2_start:
    ; Insert stage2 binary here. It is done this way since we
    ; can determine the size(and number of sectors) to load since
    ;     Size = stage2_end-stage2_start
    incbin "stage2.bin"

; End of stage2. Make sure this label is LAST in this file!
stage2_end:

; Fill out this file to produce a 1.44MB floppy image
TIMES 1024*1440-($-$$) db 0x00

kernel.c:

extern void print_char(const char inchar);

void print_string(const char *string)
{
    while (*string)
        print_char(*string++);
}

void kmain(unsigned int drive_num)
{
    (void) drive_num;          /* Quiet compiler warning / unused variable */

    print_char('A');           /* Print A */
    print_char(13);            /* Print Carriage Return */
    print_char(10);            /* Print Line Feed */
    print_string("Hello, world!\r\nThis is a test!\r\n");

    return;
}

tty.asm:

bits 16
global print_char

print_char:
    ; Removed the prologue and epilogue code as it isn't needed

    push bx         ; BX is non volatile register we need to save it
    mov ah, 0eh     ; set code for printing
    mov al, [esp+6] ; move char into al
    xor bx, bx      ; Ensure page 0 (BH = 0), BL is color if in graphics mode
    int 10h         ; call int 10h
    pop bx          
    o32 ret         ; We need to do a long return because the return address
                    ;     the C code put on the stack was a 4 byte return address.
                    ;     Failure to get this right can corrupt the stack

entry.asm:

bits 16

extern kmain
extern __bss_start
extern __bss_sizel
global _start

; The linker script will place .text.entry before other sections.
section .text.entry

_start:
    ; DL - drive number we booted as
    xor ax, ax         ; DS=ES=SS=0 (CS was already set to 0)
    mov es, ax
    mov ds, ax
    mov ss, ax
    mov esp, 0x7c00-16 ; SS:SP is 0x0000:0x7c00 below the bootloader
                       ; Create stack space to pass drive number as parameter and
                       ;     ensure ESP is still 16 byte aligned before calling kmain
    finit              ; Initialize x87 FPU
    cld                ; Set Direction Flag (DF) is cleared (forward movement)
    sti                ; Enable interrupts

    ; Zero out the BSS memory area a DWORD at a time
    ; since the memory isn't guaranteed to already be zero
    xor eax, eax
    mov ecx, __bss_sizel
    mov edi, __bss_start
    rep stosd

    movzx edx, dl      ; Zero extend drive number to 32-bit value
    mov [esp], edx     ; Pass drive number as first parameter to kmain
    call dword kmain   ; We need to call C functions with `dword` so a 32-bit
                       ;     return address is on the stack 
    
.hltloop:              ; Infinite loop to end the kernel
    hlt
    jmp .hltloop

link.ld:

OUTPUT_FORMAT(elf32-i386)

SECTIONS {
    . = 0x7e00;

    .text : SUBALIGN(4)
    {
        *(.text.entry)       /* Ensure .text.entry appears first */
        *(.text*)
        *(.rodata*)
        *(.data)
    }

    .bss : SUBALIGN(4) {
        __bss_start = .;
        *(COMMON)            /* all COMMON sections from all files */
        *(.bss)              /* all BSS sections from all files */
    }
    . = ALIGN(4);
    __bss_end = .;
    __bss_sizeb = __bss_end - __bss_start;       /* BSS size in bytes */
    __bss_sizel = (__bss_end - __bss_start) / 4; /* BSS size in longs/DWORDs */

    /DISCARD/ : {            /* Remove Unneeded sections */
        *(.eh_frame);
        *(.comment);
    }

    __end = .;
}

In order to compile/assemble/link this you can use these commands:

# Build kernel assembly files
nasm -f elf32 entry.asm -o entry.o
nasm -f elf32 tty.asm -o tty.o

# Compile the C files
gcc -c -Wall -Wextra -m16 -O3 -ffreestanding -fno-exceptions \
    -fno-asynchronous-unwind-tables -fno-pic kernel.c -o kernel.o

# Link the files to an 32-bit ELF executable using a linker script
ld  -Tlink.ld -melf_i386 -nostartfiles -nostdlib \
    tty.o entry.o kernel.o -o kernel.elf

# Convert the ELF executable to a binary file that can be loaded by the bootloader
objcopy -O binary kernel.elf stage2.bin

# Generate the bootloader/disk image
nasm -f bin boot.asm -o disk.img

You can run it with QEMU using:

qemu-system-i386 -fda disk.img

You can run it with BOCHS using:

bochs -qf /dev/null 'boot:floppy' \
    'floppya: type=1_44, 1_44="disk.img", status=inserted, write_protected=0'

I recommend BOCHS for debugging real mode code. When run it should output something similar to:

enter image description here

Michael Petch
  • 46,082
  • 8
  • 107
  • 198