jonesforth is typically started as follows:
cat jonesforth.f - | ./jonesforth
What\'s a good way to debug jonesforth?
If you're on Ubuntu, allow gdb to attach to running processes:
echo 0 > /proc/sys/kernel/yama/ptrace_scope
If you'd like that setting to remain across reboots:
vim /etc/sysctl.d/10-ptrace.conf
Add the g flag to your jonesforth Makefile recipe:
jonesforth: jonesforth.S
gcc -g -m32 -nostdlib -static $(BUILD_ID_NONE) -o $@ $<
Then, start up jonesforth as usual in a terminal:
cat jonesforth.f - | ./jonesforth
In another terminal, start gdb and attach it to the running jonesforth:
gdb --quiet --pid=`pgrep jonesforth` ./jonesforth
Here's what I see when I start gdb:
$ gdb --quiet --pid=`pgrep jonesforth` ./jonesforth
Reading symbols from ./jonesforth...done.
Attaching to program: /home/dharmatech/Dropbox/Documents/jonesforth-annexia/jonesforth, process 3406
_KEY () at jonesforth.S:1290
1290 test %eax,%eax // If %eax <= 0, then exit.
(gdb)
Jonesforth is waiting for us to enter something. It's in the _KEY assembly routine. This is indicated by gdb above. It also shows that line 1290 is the next one to execute. Here's the _KEY routine:
_KEY:
mov (currkey),%ebx
cmp (bufftop),%ebx
jge 1f // exhausted the input buffer?
xor %eax,%eax
mov (%ebx),%al // get next key from input buffer
inc %ebx
mov %ebx,(currkey) // increment currkey
ret
1: // Out of input; use read(2) to fetch more input from stdin.
xor %ebx,%ebx // 1st param: stdin
mov $buffer,%ecx // 2nd param: buffer
mov %ecx,currkey
mov $BUFFER_SIZE,%edx // 3rd param: max length
mov $__NR_read,%eax // syscall: read
int $0x80
test %eax,%eax // If %eax <= 0, then exit.
jbe 2f
addl %eax,%ecx // buffer+%eax = bufftop
mov %ecx,bufftop
jmp _KEY
2: // Error or end of input: exit the program.
xor %ebx,%ebx
mov $__NR_exit,%eax // syscall: exit
int $0x80
_KEY uses some variables in memory: buffer, currkey, and bufftop. It also uses a couple of registers. Let's use gdb's Auto Display feature to display these:
display/8cb &buffer
display/1xw &currkey
display/1xw &bufftop
display/x $eax
display/x $ebx
Now if we type display in gdb, we'll see all of those at once:
(gdb) display
1: x/8cb &buffer
0x804c000: 97 'a' 98 'b' 108 'l' 121 'y' 46 '.' 32 ' ' 32 ' ' 84 'T'
2: x/xw &currkey 0x8049d54: 0x0804c000
3: x/xw &bufftop 0x8049d58: 0x0804c7e3
4: /x $eax = 0xfffffe00
5: /x $ebx = 0x0
This might also be a good time to enable gdb's TUI:
tui enable
gdb should now look like this:
OK, jonesforth is still waiting for input. So let's give it something:
JONESFORTH VERSION 47
14499 CELLS REMAINING
OK 123
Alright, back in gdb, we can finally ask it to step:
(gdb) s
1: x/8cb &buffer
0x804c000: 49 '1' 50 '2' 51 '3' 10 '\n' 46 '.' 32 ' ' 32 ' ' 84 'T'
2: x/xw &currkey 0x8049d54: 0x0804c000
3: x/xw &bufftop 0x8049d58: 0x0804c7e3
4: /x $eax = 0x4
5: /x $ebx = 0x0
Hey, look at that! The first 3 characters in buffer are 1, 2, and 3.
If %eax <= 0 the next step will jump to the 2f label. But as we can see above, %eax is 4. So it should just continue on.
If we step through the next three lines, the bufftop will be set to the address of buffer incremented by 4 (three characters of '123' plus a newline character). The value in relation to the address of buffer checks out:
3: x/xw &bufftop 0x8049d58: 0x0804c004
Now that data has been read into the input buffer, _KEY will do its job and return back to the caller. Here's the next few instructions before the return:
As you step through those, the auto display feature will show the variables and registers updating accordingly.
My (quite limited!) experience is that the LLVM debugger lldb is much friendlier when tinkering with assembly-language programs.