I can assemble with and without
section my_main vstart=0
but with it, the bochs emulator claim that there is no bootable disk.
jmp $
times 510-($-$$) db 0
db 0x55
db 0xaa
without it, bochs claim nothing.
section my_main vstart=0
here:
%include 'main.s'
times 1474560 - ($-$$) db 0
how can I change
label "here" from 512 to 0?
section my_main vstart=N change value of $ and $$
Thus times directive was affected by the calculated value.
As a result, the floppy image size changed.
And unable to boot.
(Because the size of floppy image does not meet the 1474560 agreement.)
Without vstart directive, the size will be 1474560.
But with the vstart directive, the image file size becomes 1474560 + 512.
Thus bochs claim to boot.
I solved the problem by changing
times 1474560 - ($-$$) db 0
to
times 1474560 - ($-$$) - loader_size db 0
I define loader_size right below boot signature.
jmp $
times 510-($-$$) db 0
db 0x55
db 0xaa
%define loader_size ($-$$)
And it works as expected!
Related
For some reason [msgLen] and msgLen produce the same result. I know that I ought to use equ or something, but I want to know why this isn't working. Is NASM ignoring the dereference? It prints my string and a bunch of junk bc it thinks the string is bigger than it is right? Thanks!
Please see the assembly below:
_start:
mov edx,[msgLen]
mov ecx,msg
mov ebx,1
mov eax,4
int 0x80
mov eax,1
int 0x80
section .data
msg db 'Zoom',0xA
msgLen db $-msg
Use a debugger to look at EDX after the load, or use strace ./a.out to see the length you actually pass to the system call. It will be different (address vs. value loaded). Both ways are buggy in different ways that happen to produce large values, so the effect only happens to be the same. Debugging / tracing tools would have clearly shown you that NASM isn't "ignoring the dereference", though.
mov edx, msgLen is the address, right next to msg. It will be something like 0x804a005 if you link into a standard Linux non-PIE static executable with ld -melf_i386 -o foo foo.o.
mov edx, [msgLen] loads 4 bytes from that address (the width of EDX), where you only assembled the size into 1 byte in the data section. The 3 high bytes come from whatever comes next in the part of the file that's mapped to that memory page.
When I assemble with just nasm -felf32 foo.asm and link with ld -melf_i386, that happens to be 3 bytes of zeros, so I don't get any garbage printed from the dword-load version. (x86 is little-endian).
But it seems your non-zero bytes are debug info. I've found NASM's debug info is the opposite of helpful, sometimes making GDB's disassembly confused (layout reg / layout asm sometimes fail to disassemble a whole block after a label) so I don't use it.
But if I use nasm -felf32 -Fdwarf, then I do get 7173 from that dword load that goes past the end of the .data section. That's a different large number, so this is just wrong in a different way, not the same problem. 7173 is 0x1c05, so it corresponds to db 5, 0x1c, 0, 0. i.e. your calculated length of 5 is the low byte, but there's a 0x1c after it. yasm -gdwarf2 gives me 469762053 = 0x1c000005.
If you'd used db $-msg, 0,0,0 or dd $-msg, you could load a whole dword. (To load and zero-extend a byte into a dword register, use movzx edx, byte [mem])
write() behaviour with a large length
If you give it some very large length, write will go until it reaches an unreadable page, then return the length it actually wrote. (It doesn't check the whole buffer for readability before starting to copy_from_user. And it returns the number of bytes written if that's non-zero before encountering an unreadable page. You can only get -EFAULT if an unreadable page is encountered right away, not if you pass a huge length that includes some unmapped pages later.)
e.g. with mov edx, msgLen (the label address)
$ nasm -felf32 -Fdwarf foo.asm
$ ld -melf_i386 -o foo foo.o
$ strace -o foo.tr ./foo # write trace to a file so it doesn't mix with terminal output
Zoom
foo.asmmsgmsgLen__bss_start_edata_end.symtab.strtab.shstrtab.text.data.debug_aranges.debug_info.debug_abbrev.debug_lin! ' 6& 9B_
$ cat foo.tr
execve("./foo", ["./foo"], 0x7fffdf062e20 /* 53 vars */) = 0
write(1, "Zoom\n\5\34\0\0\0\2\0\0\0\0\0\4\0\0\0\0\0\0\220\4\10\35\0\0\0\0\0"..., 134520837) = 4096
exit(1) = ?
+++ exited with 1 +++
134520837 is the length you passed, 0x804a005 (the address of msgLen). The system call writes 4096 bytes, 1 whole page, before getting to an unmapped page and stopping early.** It doesn't matter number you pass higher than that, because there's only 1 page before the end of the mapping. (And msg: is apparently right at the start of that page.)
On the terminal (where \0 prints as empty), you mostly just see the printable characters; pipe into hexdump -C if you want a better look at the binary data. It includes bits of metadata from the file, because the kernel's ELF program loader works by mmaping the file into memory (with a MAP_PRIVATE read-write no-exec mapping for that part). Use readelf -a and look at the ELF program headers: they tell the kernel which parts of the file to map into memory where, with what permissions.
Fun fact: If I redirect to /dev/null (strace ./foo > /dev/null), the kernel's write handler for that special device driver doesn't even check the buffer for permissions, so write() actually does return 134520837.
write(1, "Zoom\n\5\0\0[...]\0\0\220\4\10"..., 134520837) = 134520837
Using EQU properly
And yes, you should be using equ so you can use the actual length as an immediate. Having the assembler calculate it and then assemble that byte into the data section is less convenient. This prints exactly the right size, and is more efficient than using an absolute address to reference 1 constant byte.
mov edx, msg.len ; mov r32, imm32
...
section .rodata
msg: db 'Zoom',0xA
.len equ $-msg
(Using a NASM . local label is unrelated to using equ; I'm showing that too because I like how it helps organize the global namespace.)
Also semi-related to how ld lays out sections into ELF segments for the program-loader: recent ld pads sections for 4k page alignment or something like that to avoid getting data mapped where it doesn't need to be. Especially out of executable pages.
As a learning experience, I'm writing a boot-loader for BIOS in NASM on 16-bit real mode in my x86 emulator, Qemu.
BIOS loads your boot-sector at address 0x7C00. NASM assumes you start at 0x0, so your labels are useless unless you do something like specify the origin with [org 0x7C00] (or presumably other techniques). But, when you load the 2nd stage boot-loader, its RAM origin is different, which complicates the hell out of using labels in that newly loaded code.
What's the recommended way to deal with this? It this linker territory? Should I be using segment registers instead of org?
Thanks in advance!
p.s. Here's the code that works right now:
[bits 16]
[org 0x7c00]
LOAD_ADDR: equ 0x9000 ; This is where I'm loading the 2nd stage in RAM.
start:
mov bp, 0x8000 ; set up the stack
mov sp, bp ; relatively out of the way
call disk_load ; load the new instructions
; at 0x9000
jmp LOAD_ADDR
%include "disk_load.asm"
times 510 - ($ - $$) db 0
dw 0xaa55 ;; end of bootsector
seg_two:
;; this is ridiculous. Better way?
mov cx, LOAD_ADDR + print_j - seg_two
jmp cx
jmp $
print_j:
mov ah, 0x0E
mov al, 'k'
int 0x10
jmp $
times 2048 db 0xf
You may be making this harder than it is (not that this is trivial by any means!)
Your labels work fine and will continue to work fine. Remember that, if you look under the hood at the machine code generated, your short jumps (everything after seg_two in what you've posted) are relative jumps. This means that the assembler doesn't actually need to compute the real address, it simply needs to calculate the offset from the current opcode. However, when you load your code into RAM at 0x9000, that is an entirely different story.
Personally, when writing precisely the kind of code that you are, I would separate the code. The boot sector stops at the dw 0xaa55 and the 2nd stage gets its own file with an ORG 0x9000 at the top.
When you compile these to object code you simply need to concatenate them together. Essentially, that's what you're doing now except that you are getting the assembler to do it for you.
Hope this makes sense. :)
how do i store 2 digit data using extra segment(es) and appropriate offset....i tired this way..
SECTION .data
offset : db 0100H
data : db 56H
SECTION .bss
.......(initializations not required in this program)
SECTION .text
global main
main :
mov ax,1000H
mov es,ax
mov si,[offset]
mov al,[data]
mov es:[si],al
mov rdi,0
mov rax,60
syscall
What is wrong in this? I'm completely new in nasm programming...
Directly accessing a certain memory address using the segent pointers only work in 16 bit real mode. In 64 bit protected mode the physical memory addresses are mapped to application visible addresses by the MMU. This is typically managed by the OS while user space applications are simply not allowed to temper with it. If you want to access certain physical memory you need to tell the OS map it into your memory space somehow.
I am trying to build a simple x86 Linux bootloader in nasm.
The Linux bzImage is stored on disk partition sda1 starting from the first sector.
I read the real mode code from the bzImage (15 sectors) into memory starting from 0x7E00.
However when i jump into the code it just hangs, nothing happens.
I have created code for the master boot record on sda. I's probably best if i just attach
the whole thing. I would like to know why it just hangs after the far jump instruction.
[BITS 16]
%define BOOTSEG 0x7C0
%define BOOTADDR (BOOTSEG * 0x10)
%define HDRSEG (BOOTSEG + 0x20)
%define HDRADDR (HDRSEG * 0x10)
%define KERNSEG (HDRSEG + 0x20)
[ORG BOOTADDR]
entry_section:
cli
jmp start
start:
; Clear segments
xor ax, ax
mov ds, ax
mov es, ax
mov gs, ax
mov fs, ax
mov ss, ax
mov sp, BOOTADDR ; Lots of room for it to grow down from here
; Read all 15 sectors of realmode code in the kernel
mov ah, 0x42
mov si, dap
mov dl, 0x80
int 0x13
jc bad
; Test magic number of kernel header
mov eax, dword [HDRADDR + 0x202]
cmp eax, 'HdrS'
jne bad
; Test jump instruction is there
mov al, byte [KERNSEG * 16]
cmp al, 0xEB
jne bad
xor ax, ax ; Kernel entry code will set ds = ax
xor bx, bx ; Will also set ss = dx
jmp dword KERNSEG:0
; Simple function to report an error and halt
bad:
mov al, "B"
call putc
jmp halt
; Param: char in al
putc:
mov ah, 0X0E
mov bh, 0x0F
xor bl, bl
int 0x10
ret
halt:
hlt
jmp halt
; Begin data section
dap: ; Disk address packet
db 0x10 ; Size of dap in bytes
db 0 ; Unused
dw 15 ; Number of sectors to read
dw 0 ; Offset where to place data
dw HDRSEG ; Segment where to place data
dd 0x3F ; Low order of start addres in sectors
dd 0 ; High order of start address in sectors
; End data section
times 446-($-$$) db 0 ; Padding to make the MBR 512 bytes
; Hardcoded partition entries
part_boot:
dw 0x0180, 0x0001, 0xFE83, 0x3c3f, 0x003F, 0x0000, 0xF3BE, 0x000E
part_sda2:
dw 0x0000, 0x3D01, 0xFE83, 0xFFFF, 0xF3FD, 0x000E, 0x5AF0, 0x01B3
part_sda3:
dw 0xFE00, 0xFFFF, 0xFE83, 0xFFFF, 0x4EED, 0x01C2, 0xb113, 0x001D
part_sda4:
dw 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000
dw 0xAA55 ; Magic number at relative address 510
mbrend: ; Relative address 512
Assuming your code is a boot loader (and therefore is not an MBR):
Never disable IRQs unless you have to. The BIOS needs them to function correctly, and will enable them inside some BIOS functions anyway (e.g. waiting for a "sectors transferred" IRQ inside disk functions). Because your code is only loading and passing control to more real mode code (e.g. and no switch to protected mode or anything is involved) you have no reason to disable IRQs anywhere in your entire boot loader.
For real mode addressing, it's typically cleaner/easier to use 0x0000:0x7C00 rather than 0x07C0:0x0000. You seem to be attempting to mix both (e.g. set segment registers for the former but define BOOTSEG and HDRSEG for the latter).
The partition table contains "extended partitions" and not "primary partitions" and your partition table is therefore wrong (and should probably be blank/empty).
The boot loader should not assume any specific/hard-coded "starting LBA" (the "starting LBA" for the partition depends on how the end user felt like partitioning their disk when the OS is installed). You need to determine the partition's "starting LBA" from the MBR's primary partition table, which is typically done by hoping that the MBR left DS:SI pointing to your partition's partition table entry.
You should not assume that your are booting from "BIOS device 0x80". The MBR should leave DL set to the correct device number, and there should be no reason why your code shouldn't work if (e.g.) the OS is installed on the second hard drive or something else.
Your hard-coded "starting LBA to read" (in the DAP) is wrong. For historical reasons there's probably 63 sectors per track and your partition starts on the 64th sector. This means that LBA sector 0x3F is the first sector in the partition (which is your boot loader) and is not the first sector of the kernel. I'm assuming the first sector of the kernel might be LBA sector 0x40 (the second sector of the partition).
The "number of sectors" shouldn't be hard-coded either. You'd want to load the beginning of the kernel and examine it, and determine how many sectors to load where from that.
Typically 512 bytes (actually more like 446 bytes) is far too little for a decent boot loader. The first 512 bytes of a boot loader should load the rest of the boot loader (with every spare byte left over used to improve error handling - e.g. puts("Read error while trying to load boot loader") rather than just putc('B')). Everything else (loading the pieces of the kernel, setting up a video mode, setting correct values in the "real mode kernel header" fields, etc) should be in the additional sectors and not be in the first sector.
Note that the way the computer boots has been carefully designed such that any MBR can chainload any OS on any partition of any disk; and the MBR may be part of something larger (e.g. a boot manager) that allows multiple OSs to be installed (e.g. where the user can use a pretty menu or something to choose which partition the MBR's code should chain-load). This design allows the user to replace the MBR (or boot manager) with anything else at any time without effecting any installed OS (or causing all of their installed OSs to need fixing). For a simple example, a user should be able to have 12 different partitions that all contain your boot loader and a separate/independent version of Linux, and then install any boot manager (e.g. GRUB, Plop, GAG, MasterBooter, etc) that they want at any time.
For why your code hangs, it's not very important given that all of the code needs to be rewritten anyway . If you're curious, I'd strongly recommend running it inside an emulator with a debugger (e.g. Bochs) so that you can examine exactly what has happened (e.g. dump memory at 0x00007E00 to see what it contains, single-step the JMP to see what is being executed, etc).
Comment does not match code!
xor bx, bx ; Will also set ss = dx
I seriously doubt if that is your problem...
Disclaimer: I have not done this! I'm "chicken" and have always done my bootin' from floppy.
What I "expect" to see in an MBR is for it to move itself out of the way, and then load the first sector on the active partition to 7C00h again, and then jump there. This "real bootloader" loads the rest. I'm not familiar with the layout of a bzImage - maybe it'll work loaded at 7E00h...
I think I'm in over my head. I'll get me coat...
I have grub v1.98 installed and after disassembling the MBR I find the following code snippet that I don't understand:
xor ax,ax
mov [si+0x4],ax
inc ax
mov [si-0x1],al
mov [si+0x2],ax
mov word [si],0x10
mov ebx,[0x7c5c]
mov [si+0x8],ebx
mov ebx,[0x7c60]
mov [si+0xc],ebx
mov word [si+0x6],0x7000
mov ah,0x42
int 0x13
It seems this piece of code tries to set up disk address of stage 1.5 code, then load and run it. However, how could I figure out which physical block it tries to read? What's more, what is the destination of the stage 1.5 code? 0x7000?
I refer to MBR for Windows 7, where subsequent boot up code is loaded 0x7c00. Given MBR is first loaded at address 0x7c00, it contains a piece of code copying MBR from 0x7c00 to 0x0600 and then branch to 0x0600 in case the original code corrupted. Will loading stage 1.5 code to address 0x7000 conflict the original code? What's more, I also find:
jmp short 0x65
nop
sar byte [si+0x7c00],1
mov es,ax
mov ds,ax
mov si,0x7c00
mov di,0x600
mov cx,0x200
cld
rep movsb
push ax
push word 0x61c
retf
at the beginning of the MBR. It seems the code tries to do the same thing as in MBR of windows 7 to copy the original MBR from 0x7c00 to 0x0600, except for the first jmp instruction. Will these codes in fact executed? If yes, when will control jumps here.(I believe the answer is YES, but am confused by the leading jmp).
GRUB 1.98 is GRUB version 2. In version 2, there is no stage 1.5 anymore.
The stage 1.5 had a fixed place between MBR and first partition. It was (most often) unused space on the hard drive. GPT partitioning and other (unusual) layouts do not provide this space.
In GRUB v2 stage 1 loads core.img, which can be stored at any LBA48 location, typically between MBR and first partition, but it can also be stored within a partition. In the non-EFI case of GPT, a custom partition should be created for it. The location is hardwired into stage 1.
See also: http://www.gnu.org/software/grub/manual/grub.html#Images