The time has come in the development of the initialization process of my OS to get details about memory. I could have done this a lot simpler as both systems I use, have 4 gigs, but on every system that I'll use my software, I want detailed reporting. This code is somewhat fashioned after the example detecting memory at wiki.osdev.org, minus some functionality and a lot less bloat.
My development environment is an HP Thinkcenter, dual boot Ubuntu 16.04 LT and DOS 6.22 with the ability to boot from USB. The code has been tested with BOCH's, DOS .com file and sector 0 of USB. The only part that hasn't been tested is if bits 31 - 2E aren't zero. Then again, I doubt seriously there will every be a single entry with a minimum length of 2 gig.
NOTE: DosBox yields blank screen under Linux
I've included the source for NASM and if anyone has some real hardware it could be tested on with a screenshot of actual results, that would be much appreciated.
The prolog and epilog for each is functionally the same, but with a few differences peculiar to each paradigm.
I use BOCH's and DEBUG without symbols, so these page alignments make it easier to remember where the buffer is and for memory dumps.
7C00 FA cli 7C01 BDF0FF mov bp, -16 Reserve space for BP 7C04 89EC mov sp, bp 7C06 83EC30 sub sp, 48 7C09 FB sti
Not going to assume BIOS passes all segment registers pointing to the same place.
7C0A 8CD0 mov ax, ss 7C0C 8ED8 mov ds, ax 7C0E 8EC0 mov es, ax
This boot sector was assembled without an ORG directive, therefore everything is an offset from zero. CS must be set appropriately.
7C10 EA1500C007 jmp 0x7c0:0x15
Clears the video page determined by DISP_PAGE, Sets the initial output to center of second line. This will be bumped by 1 later, so values actually start on line 3.
7C15 E8CF00 call ClrScr 7C18 C746009001 mov [bp], 0x190 7C1D 89E7 mov di, sp DI = Pntr to E820 data 7C1F E82E00 call GetMap
In this case as DISP_PAGE is defined as 0, 7C22 -> 7C2A and 7C31 -> 7C38 are redundant, but in the event value is changed in line 10 of source, this needs to be here.
7C22 B80005 mov ax, SAP << 16 7C25 50 push ax 7C26 B000 mov al, DISP_PAGE 7C28 CD10 int VIDEO Wait for reponse from operator and convert to uppercase 7C2A B80000 mov ax, 0x0 7C2D CD16 int KEYBOARD 7C2F 245F and al, 0x5f 7C31 67870424 xchg ax,[esp] 7C35 CD10 int VIDEO 'Y' or 'y' will reboot, anything else hangs 7C37 58 pop ax 7C38 3C59 cmp al, 'Y' 7C3A 7502 jnz 7c3e 7C3C CD19 int REBOOT 7C3E F4 hlt 7C3F EBFE jmp $
To calculate breakpoints, add this value to desired address in application body.
7C50 GetMap 253 NULLS to padd object code to boot signature 7DFE 55 AA
100 BCF0FF mov sp, -16 103 C81E0000 enter 30, 0 107 E8BD00 call ClrScr 10A C746009001 mov [bp],0x190 10F 89E7 mov di, sp DI = FFC0 111 E81C00 call GetMap
Unlike the original version of DEBUG that came bundled with DOS 6.22, I use a version that is 32 bit capable, but if one displays and debugs on same page, it just becomes a big mess, so output is directed to page 1 @ B9???. To see the results, I switch to that page, wait for any kind of response and then revert to page 0 again.
114 B80005 mov ax, SAP << 16 AH = 5 Set active page 117 50 push ax 118 B001 mov al, DISP_PAGE 11A CD10 int VIDEO 11C B80000 mov ax, GETKEY 11F CD16 int KEYBOARD 121 58 pop ax 122 CD10 int VIDEO 124 C9 leave 125 CD20 int TERMINATE
To calculate breakpoints, add this value to desired address in application body.
This code is common to both BOOT & DOS. To determine absolute address add value denoted by GetMap in previous examples.
Initialize registers required by INT 15 0 6631DB xor ebx, ebx 3 6689D9 mov ecx, ebx 6 B120 mov cl, 32 8 66BA50414D53 mov edx, 534d4150 'SMAP' E B820E8 mov ax, 0xe820
Loop until BL = 0, CF = 1 or EAX <> EDX when BL = 1
11 50 push ax 12 6652 push edx 14 C6451401 mov [di+20], 1 ACPI 3.0 18 CD15 int SYS_SERVICE 1A 7211 jc 0x2d 1C 80FB01 cmp bl, 1 1F 720C jc 0x2D 21 7707 ja 0x2A Not first iteration 23 66673B0424 cmp eax,[esp] EAX = 'SMAP' 28 7503 jnz 0x2D 2A E80600 call 0x33 2D 665A pop edx 2F 58 pop ax 30 73DF jnc 0x11 Read another entry 32 C3 ret
Being in real mode (16 bit) were limited to 32 bit data, so these 64 bit values passed by BIOS need to be digested in 32 bit chunks.
33 51 push cx Preserve continuation 34 89FE mov si, di 36 8B4602 mov ax,[bp+2] Video segment 39 06 push es 3A 8EC0 mov es, ax 3C 57 push di ; At the beginning of each conversion, bump to center of next line 3D 8B7E00 mov di,[bp] 40 81C7A000 add di,160 44 897E00 mov [bp], di
BIOS colors correspond directly to map types which are displayed on center column
47 8A6410 mov ah,[si+16] 4A B0FE mov al,0xfe 4C AB stosw 4D 83EF06 sub di, 6 Back to left of center 50 57 push di
The loop between 51 <-> 77 needs to be executed twice. Once for base address and then again for length of entry. This next instruction facilitates that without the need for excessive jumps or calls.
NOTE: This is the only instruction that relies on ORG 100H as it is absolutely unlike everything else that is relative.
51 689D01 push 0x19D
One thing I've never liked is leading zeros unless this is a specific reason for it, so this part only displays what needs to be.
54 66AD lodsd Bits 31 - 0 56 E81F00 call 0x78 59 66AD lodsd Bits 63 - 32 5B 6609C0 or eax, eax At this point, if EAX = 0 we don't need to padd with CL zero's 5E 7501 jnz 0x61 60 C3 ret
CL = positions left to fill with '0' to complete all 8. Should have maybe tested for zero, but REP STOSW won't do anything if so
61 6650 push eax 63 B83007 mov ax,0x730 Attr in AH = White 66 FD std Need to do in reverse 67 F3AB rep stosw 69 6658 pop eax Restore map value Because we don't care about CL and DF is already set, we can simply drop into procedure that actually starts @ 0x78 6B EB0E jmp 0x7B This is where lenth of map segment is displayed 6D 5F pop di 6E 83C728 add di, 40 71 E8E0FF call 0x54 Restore and read next map entry 74 5F pop di 75 07 pop es 76 59 pop cx 77 C3 ret
This routine converts binary value in EAX to hexidecimal notation (upper case)
78 B108 mov cl, 8 Max digits 7A FD std Set DF to decreemnt Save value of EAX and then convert AL to ASCII equivalent 7B 6650 push eax 7D 240F and al, 0xf 7F 3C0A cmp al, 0xa 81 7202 jc 0x85 83 0407 add al, 0x7 10-15 become A-F 85 0430 add al, 0x30 87 B407 mov ah, 0x7 Display in White 89 AB stosw 8A 6658 pop eax 8C FEC9 dec cl Bump digits displayed 8E 66C1E804 shr eax, 4 92 75E7 jnz 0x7b While EAX 94 FC cld Reset DF to inc 95 F8 clc So primary loop won't bail. 96 C3 ret
Clearing the screen could have been done by setting video mode, but for some reason on my test machine, this takes probably 2.5 seconds, so I implemented this instead.
97 06 push es 98 B800B8 mov ax, 0xb800 9B 80C401 add ah, DISP_PAGE 9E 8EC0 mov es, ax A0 894602 mov [bp+2], ax A3 57 push di A4 31FF xor di, di A6 B82007 mov ax, 0x720 White on Black with spaces A9 51 push cx AA B9D007 mov cx, 0x7d0 80 x 25 words AD F3AB rep stosw AF 59 pop cx B0 5F pop di B1 07 pop es B2 C3 ret
Assemble as follows for
nasm E820.asm -D_BOOT_ -oBoot.bin
nasm E820.asm -oE820.com
Haven't quite figured out why sometimes source tabulates properly and sometimes it doesn't, but anyway I sure if you copy this into an editor of your choice, it will look a lot better
SAP equ 5 ; Function to set active page GETKEY equ 0 ; Read keyboard input VIDEO equ 16 ; BIOS Video services KEYBOARD equ 22 BOOT equ 25 SYS_SERVICE equ 21 %ifdef _BOOT_ DISP_PAGE equ 0 ; Choose page 0 for Bochs cli ; Disable interrupts mov bp, -16 ; Scatch area for BP mov sp, bp sub sp, 48 ; Space for map entries (page aligned) sti ; BIOS should probably of set these, but just in case mov ax, ss mov ds, ax mov es, ax ; NOTE: Because original is at zero so quirky BIOS's won't break my code. jmp 0x7c0:Start %else DISP_PAGE equ 1 ; Choose page 1 for DOS & DEBUB org 256 ; For DOS com files mov sp, -16 ; 16 Byte scratch area for BP enter 30, 0 %endif Start: call ClrScr ; Clear selected display page mov word [bp], 400 ; Ouput begins right justifed Line=3 Col=40 mov di, sp ; Point to E820 entry buffer call GetMap ; This is for the benefit of BOCHS or maybe even QEMU and DOS so result ; can be viewed before re-booting or alternatively returning to DEBUG mov ax, 0x500 ; Select page 0 push ax mov al, DISP_PAGE ; Display results page int VIDEO mov ax, GETKEY int KEYBOARD ; Wait for reponse from operator %ifdef _BOOT_ and al, 0x5f ; xchg ax, [esp] %else pop ax %endif int VIDEO ; Set to default video page 0 %ifdef _BOOT_ pop ax cmp al, 'Y' jnz $ + 4 int BOOT ; Let's do it again hlt jmp $ ; Spin %else leave ; Kill procedure frame int 32 ; Terminate DOS application %endif ; This is here so the object code for BOOT version versus DOS version ; is at the same offset from the beginning of executable. align 16 GetMap: xor ebx, ebx ; Continuation starts @ 0 mov ecx, ebx mov cl, 32 ; Anything greater than 20 mov edx, 'PAMS' mov ax, 0xe820 ; Inifinte loop unless ; A: RBX = 0 ; B: EAX <> EDX ; C: Service call returns CF = 1 .L0: push ax push edx mov byte [di+20], 1 ; This has something to do with ACPI 3 int SYS_SERVICE jc .done ; Call return CY=1 cmp bl, 1 jb .done ; BL = 0 ja .done - 3 ; Not first time cmp eax, [esp] ; Compare values jnz .done call DMap ; Convert and display two 64 bit values .done: pop edx pop ax jnc .L0 ; This might break if EAX <> EDX ret DMap: push cx ; Save continue value for INT 15H mov si, di ; Pointer to 4 64 bit values mov ax, [bp+2] ; Get video segment push es mov es, ax push di ; Preserve address to map data mov di, [bp] ; Next position to write to in video add di, 160 ; Bump to next line mov [bp], di ; and save mov ah, [si+16] ; Get entry type mov al, 254 ; Small block stosw ; Set entry type indicator sub di, 6 ; Bump back to left side push di push .C0 ; Re-entrant ; First invocation display base address of entry, next time length. .J0: lodsd ; Convert and display low order 32 bits call WVal lodsd or eax, eax jnz $ + 3 ret push eax mov ax, 0x730 std rep stosw ; Write CL leading zeros pop eax jmp WVal.J0 ; Drop into because DF is already set .C0: pop di add di, 40 ; Right most position of call .J0 ; Display length on entry pop di pop es pop cx ret ; Converts and display 32-bit value in EAX in reverse order WVal: mov cl, 8 std .J0: push eax and al, 15 cmp al, 10 jb $ + 4 add al, 7 add al, '0' mov ah, 7 stosw pop eax dec cl shr eax, 4 jnz .J0 ; In the event leading zero's need to be displayed, the number is returned ; in CL cld ; Re-set direction flag clc ; So primary loop won't break ret ClrScr: push es mov ax, 0xb800 add ah, DISP_PAGE mov es, ax mov [bp+2], ax push di xor di, di mov ax, 0x720 ; White on Black / spaces push cx mov cx, 80*25 rep stosw pop cx pop di pop es ret %ifdef _BOOT_ times 510 - ($-$$) db 0 dw 0xAA55 %endif