colinh
Posts: 95
Joined: Tue Dec 03, 2013 11:59 pm
Location: Munich

Examining Alex's Screen03 on qemu using gdb

Fri Apr 03, 2015 7:02 pm

This is a slightly odd (and long) post, since it doesn't pose any immediate questions (although I'd appreciate any corrections/clarifications), but rather documents my attempts at getting everything running, in the hope that some of it might be useful to a beginner...

Due to the temporary lack of a JTAG interface I thought I'd try out qemu. I used Torlus's branch mentioned in a recent post (running ./configure --target-list=arm-softmmu lets it compile much quicker by leaving out all the other processors).

I thought I'd try it out on Alex Chadwick's Screen0x tutorials which I had lying around. Screen01 and Screen02 ran fine, and really fast!

qemu-system-arm -M raspi -cpu arm1176 -m 512 -kernel screen03/kernel.img

This just showed a black screen, however. Time to try out gdb.

I've just switched from Mac OS X to Linux, so I have a fairly fresh installation (Mint, which is based on Ubuntu, based on Debian, I think), and need to install various tools as and when I notice that they're not there.

apt-get install arm-none-eabi-gdb complained. I think because it conflicted with gdb's manpage, or something trivial like that. Anyway, I just purged gdb. (slight overkill, perhaps :-) )

Then I decided that gdb's, ummm, user interface, could be improved upon. I found ddd, which is ok. It can display a gdb console, the disassembled code and the source code in separate panes, and the registers and breakpoints in separate windows. I'm a gdb novice, but I sort of know what I expect it to be able to do. ddd gets called like this:

ddd --debugger arm-none-eabi-gdb

After a bit of confusion, reading and experimentation I found I needed to run the following commands in the gdb console:

(gdb) directory screen03/source
(gdb) file screen03/build/output.elf
(gdb) target remote:1234

( (gdb) is gdb's prompt. I haven't shown gdb's responses. The paths depend on what the current working directory was when ddd/gdb was started. )

I also found I needed to add the --gstabs flag to the assembler command in the Makefile:

# Rule to make the object files.
$(BUILD)%.o: $(SOURCE)%.s $(BUILD)
$(ARMGNU)-as --gstabs -I $(SOURCE) $< -o $@

Then I commented out the "fun trick" in kernel.ld, because I really wanted to get those STABS :evil:

/*
* Finally comes everything else. A fun trick here is to put all other
* sections into this section, which will be discarded by default.
/DISCARD/ : {
*(*)
}
*/

At this point I need to explain something. ARM code expects its interrupt table to be located at memory location 0x00000000 (although I think nowadays some flag can be set in some processor register, to make it use some "high" memory location instead. But, by default, it's still to be located at zero). Linux likes to start at 0x00008000, at least it does on the Raspberry Pi. That's why the linker script has a

.init 0x8000 : {

line in it. The Raspberry Pi bootcode writes some ATAGS at memory location 0x0000100 and then starts execution at 0x00000000 where a couple of instructions load some registers R0, R1 and R2 with something or other, before jumping to 0x00008000. All this, because of Linux.

There is a boot option one can set, namely

kernel_old=1
disable_commandline_tags=1

in the config.txt file on your boot SD card, which will give you the good old-fashioned "start executing my code at 0x00000000, and don't scribble all over it, dammit!", but nobody uses it (except me :-) ). Actually, nowadays I use dwelch's armjtag bootloader, so I don't use it either.

The other way to get around things is to have code at 0x00008000 copy your interrupt table to 0x00000000. (This particular example, Screen03, don't actually set up / use the interrupt vector table at all, but the "where the code is" bit is still relevant).

So far, so complicated. Qemu on the other hand, says that linux starts at 0x00010000. But everybody's code is set up to start at 0x00008000. It looks like the fix was to copy all the code from 0x8000 to 0x10000 (is that right?). That's OK, but then gdb doesn't recognise the addresses and won't display the source code.

(gdb) disassemble/mr 0x0,+0x20
Dump of assembler code from 0x0 to 0x20:
=> 0x00000000: 00 00 a0 e3 mov r0, #0
0x00000004: 04 10 9f e5 ldr r1, [pc, #4] ; 0x10
0x00000008: 04 20 9f e5 ldr r2, [pc, #4] ; 0x14
0x0000000c: 04 f0 9f e5 ldr pc, [pc, #4] ; 0x18
0x00000010: 42 0c 00 00 andeq r0, r0, r2, asr #24 [actually data: 0x00000c42]
0x00000014: 00 01 00 00 andeq r0, r0, r0, lsl #2 [0x00000100, where the ATAGS are]
0x00000018: 00 00 01 00 andeq r0, r1, r0 [0x00010000, address loaded into PC]
0x0000001c: 00 00 00 00 andeq r0, r0, r0
End of assembler dump.


I actually changed the data using

(gdb) set {int}0x18 = 0x8000

When I did

(gdb) load

gdb's response to the command was:

Loading section .init, size 0x4 lma 0x8000
Loading section .text, size 0x468 lma 0x8004
Loading section .data, size 0x2024 lma 0x9000
Start address 0x8004, load size 9360
Transfer rate: 9140 KB/sec, 1337 bytes/write.

I finally got the source code displayed! (No source is displayed for the first few instructions at 0x00000000, because there isn't any source for those.) I don't know why it set the start address to 0x8004 though.

A final

(gdb) set $pc = 0x8000

and I was ready to go (single stepping with stepi, setting breakpoints etc), at last. I don't know why, but the load command was necessary. As is the subsequent setting of the PC. You don't need to change the address at 0x18 and start from 0x0. It's ok to set the PC to 0x0. Or 0x10000. Or even 0x10388.

Oh, and the screen was blank, because there's no cmdline atag, so FindTag returns R0=0. Which is bad for the subsequent ldr r1,[r0]...

(gdb) x/64xw 0x100
0x100: 0x00000005 0x54410001 0x00000001 0x00001000
0x110: 0x00000000 0x00000004 0x54410002 0x1c000000
0x120: 0x00000000 0x00000000 0x00000000 0x00000000
0x130: 0x00000000 0x00000000 0x00000000 0x00000000
0x140: 0x00000000 0x00000000 0x00000000 0x00000000

If you comment out the FindTag subroutine, and create your own string, it works.

/* NEW
* Find the cmdline tag.
mov r0,#9
bl FindTag
*/

/* NEW
* Draw our command line.
ldr r1,[r0]
lsl r1,#2
sub r1,#8
add r0,#8
*/

ldr r0, =hello
mov r1, #12
mov r2,#0
mov r3,#0

bl DrawString

loop$:
b loop$

.section .data
.global hello
hello:
.ascii "Hello world!"

User avatar
edargelies
Posts: 7
Joined: Sun Aug 09, 2015 11:47 pm
Location: Oakland, CA
Contact: Website

Re: Examining Alex's Screen03 on qemu using gdb

Tue Oct 13, 2015 7:14 am

Hey, I just completed screen03 on the raspberry pi 2 and had the same issue. I didn't have to modify the config.txt at all. I made a post about it on another thread. Hope this will be of some use to others. viewtopic.php?p=828386#p828386

Eric

Return to “Bare metal, Assembly language”