Calculating padding length with GAS AT&T directives for a boot sector?
Asked Answered
H

2

6

So I want to add padding in the bootsector. Let's say, there is currently just an endless loop in there: jmp .. The sector needs to be 512 bytes long. Also, the magic num 0xaa55 is needed which is added at the end.

jmp .
.skip 508, 0
.word 0xaa55

But what if I want to print something but don't want to count all the bytes to pad it into the right size?
In Intel/NASM syntax would it be:

; print something
times 510-($-$$) db 0
dw 0xaa55

But in AT&T syntax? Well a loop (.rept) doesn't work here because . doesn't give an absolute value which is needed here. We have the same problem with .skip/.space, they need an absolute value too.

Is there a method to add padding using some sort of loop/.align/.skip/etc?

EDIT: I use as to build and for linking ld -Ttext 0x7c00 --oformat binary until yasm is stable enough for AT&T syntax.

Halfbound answered 17/12, 2017 at 20:44 Comment(2)
Just to nit-pick, your "Intel syntax" example is specifically NASM or MASM syntax. GAS .intel_syntax still uses . instead of $, and .word instead of dw. Directives and pseudo-instructions aren't part of the syntax that Intel specifies in their manuals.Grit
The idea you have will work assuming that there is only one source file containing your bootloader. If you try to make your bootloader with multiple assembly files and link the files together this method won't work. The default linker script that LD will use will likely create a bootloader that is improperly laid out.Ranket
R
14

With AT&T syntax you can put a label at the start of your bootloader and then use something like this:

.global _start
.text
.code16
_start:
    jmp .

.space 510-(.-_start)
.word 0xaa55

Period . is the current location counter relative to the beginning of the current section. The difference between period . and _start is an absolute value so should work in this expression.

You can use GCC (that will invoke LD) to assemble this to a bootloader with a command like:

gcc -Wl,--oformat=binary -Wl,-Ttext=0x7c00 -Wl,--build-id=none \
    -nostartfiles -nostdlib -m32 -o boot.bin boot.s

The option -Wl,--oformat=binary passes this option to the linker which will force it to output to a flat binary file. -Wl,-Ttext=0x7c00 will pass this option to the linker that will effectively set the origin point to 0x07c00. -Wl,--build-id=none tell the linker not to use the build id that GCC may generate. 0x7c00 is the offset the code is expected to be loaded at. Since we can't use a standard library or C runtime we exclude them with -nostartfiles -nostdlib

You won't be able to use this method if you intend to link multiple files together. In that case you will need to leave the boot signature out of the code and let the linker take care of it with a specially crafted linker script. The method above will work if you contain your bootloader to a single assembly file.


I have some general bootloader tips for writing bootloader code. One big issue people usually have is not setting the segment registers up. If you use an origin point of 0x7c00 then you need to make sure at a minimum that the DS register us set to 0. That will be important if you write code that uses memory operands that reference a label within your code.

When assembling with GNU assembler ensure that you set the proper instruction encoding you want. .code16 will make the assembler assume the target processor is running in 16-bit mode. .code32 for 32-bit encoding, .code64 assumes 64-bit encoding. The default for as is generally never .code16.


Bootloader with Multiple Object Files

As I mentioned above using multiple object files to create your bootloader presents challenges that can't be overcome with assembly directives. In order to do this you can create a special linker script that sets the Origin point to 0x7c00 and lets the linker place the boot signature in the output file. Using this method you don't need to do any padding, the linker will do it for you. A basic linker script that deals with traditional sections like .text, .data, .rodata is shown below. You may never use some of the section, but I added them as an example:

File bootloader.ld

OUTPUT_FORMAT("elf32-i386");
ENTRY(_start);
SECTIONS
{
    . = 0x7C00;
    /* Code section, .text.bootentry code before other code */
    .text : SUBALIGN(0) {
        *(.text.bootentry);
        *(.text)
    }

    /* Read only data section with no alignment */
    .rodata : SUBALIGN(0) {
        *(.rodata)
    }

    /* Data section with no alignment */
    .data : SUBALIGN(0) {
        *(.data)
    }

    /* Boot signature at 510th byte from 0x7c00 */
    .sig : AT(0x7DFE) {
        SHORT(0xaa55);
    }

    /DISCARD/ : {
        *(.eh_frame);
        *(.comment);
        *(.note*);
    }
}

File boot.s containing main entry point of bootloader:

# Section .text.bootentry is always placed before all other code and data
# in the linker script. If using multiple object files only specify
# one .text.bootentry as that will be the code that will start executing
# at 0x7c00

.section .text.bootentry
.code16
.global _start
_start:
    # Initialize the segments especially DS and set the stack to grow down from
    # start of bootloader at _start. SS:SP=0x0000:0x7c00
    xor %ax, %ax
    mov %ax, %ds
    mov %ax, %ss
    mov $_start, %sp
    cld                   # Set direction flag forward for string instructions

    mov  $0x20, %al       # 1st param: Attribute black on green
    xor  %cx, %cx         # 2nd param: Screen cell index to write to. (0, 0) = upper left
    mov  $boot_msg, %dx   # 3rd param: String pointer
    call print_str

    # Infinite loop to end bootloader
    cli
.endloop:
    hlt
    jmp .endloop

.section .rodata
boot_msg: .asciz "My bootloader is running"

File aux.s with a simple function to display a string directly to screen:

.global print_str         # Make this available to other modules
.section .text
.code16

# print_str (uint8_t attribute, char *str, uint16_t cellindex)
#
# Print a NUL terminated string directly to video memory at specified screen cell
# using a specified attribute (foreground/background)
#
# Calling convention:
#     Watcom
# Inputs:
#     AL = Attribute of characters to print
#     CX = Pointer to NUL terminated string to print
#     DX = Screen cell index to start printing at (cells are 2 bytes wide)
# Clobbers:
#     AX, ES
# Returns:
#    Nothing

print_str:
    push %di
    push %si

    mov  $0xb800, %di     # Segment b800 = text video memory
    mov  %di, %es
    mov  %cx, %di         # DI = screen cell index (0 = upper left corner)
    mov  %dx, %si         # SI = pointer to string (2nd parameter)
    mov  %al, %ah         # AH = attribute (3rd parameter)
    jmp  .testchar

# Print each character until NUL terminator found
.nextchar:
    stosw                 # Store current attrib(AH) and char(AL) to screen
                          # Advances DI by 2. Each text mode cell is 2 bytes
.testchar:
    lodsb                 # Load current char from string into AL(advances SI by 1)
    test %al, %al
    jne  .nextchar        # If we haven't reach NUL terminator display character
                          #     and advance to the next one

    pop %si
    pop %di
    ret

To build this bootloader to a file called boot.bin we could do something like:

as --32 aux.s -o aux.o
as --32 boot.s -o boot.o
ld -melf_i386 --oformat=binary -Tlink.ld -nostartfiles -nostdlib \
    aux.o boot.o -o boot.bin

The special .text.bootentry is placed as the first code by the linker script. This section should only be defined in one object file as it will be the code that appears right at the beginning of the bootloader at 0x7c00. The linker script adjusts the VMA (origin) to 0x7dfe and writes the boot signature(0xaa55). 0x7dfe is 2 bytes below the end of the first 512 bytes. We no longer do any padding in the assembly code nor do we emit the boot signature there.

When run this sample bootloader should print a string to the upper left of the display with black on a green background.

Ranket answered 17/12, 2017 at 20:58 Comment(2)
The clean way to do this is of course to use a linker script to place the bits and pieces into appropriate locations, but this should do, too.Taeniasis
@Taeniasis you are correct, but this is still valid as long as there is a single file. He has other issues if he is linking objects together for a bootloader. I have another answer somewhere about using gnu assembler for bootloaders that shows such a linker script (for LD). if the OP is doing something like that. I may have made a false assumption about that but the OP doesn't say how he's assembling this thing.Ranket
D
10

You can do this very simply with the .org directive:

    .code16
    .text
    jmp     .
    .org    510
    .word   0xaa55

The .org directive advances the location counter (.) to the value given, filling any skipped locations with zeroes (by default).

Note that location counter is relative to the start of the current section in the object file being generated. This makes the .org directive above the same thing as doing .space 510-(.-.text) where .text is the start of .text section in the current object, not the final linked output. This means that it only really works when creating a bootloader from a single assembly file.

Diplopod answered 17/12, 2017 at 22:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.