|
I have had a couple of sessions at trying to get this up and running and I
had to repeat some of the work in the second session from the first since I
had forgotten some of what I had done. Hence the notes.
Bernhard Kuhn had also got most of this working I used some of his notes
during this session.
Erwin Authried also got quite a bit working for his Armtwister
project. I have just managed to combine the work of both and get
something working for now.
I was also working with the Midori distribution modified for the Arm.
You will have to adjust some of the locations of the files for other
distributions.
A word on gdb. I have not found out exactly how the details of all the gdb
code works here.
It looks as though it may be easy to understand once you find the right keys.
I have not yet found those keys.
It has so many options and defaults that it
is difficult to work out who is doing what at any time. I have never really
tried to trace the code before. Once you get a handle on the small section
you are interested in you tend to give up the search.
The debug options on gdb (set debug remote 1 ) provided most of the answers.
Gdbserver
==========
There are a few immediate problems with gdbserver.
First it uses fork and we only have vfork
the change in low-linux.c is:
{
int pid;
- pid = fork ();
+ pid = vfork();
if (pid < 0)
perror_with_name ("fork");
Again you may have problems with perror etc..
If so remove the "proper" error reporting and use your own error message
Also, depending on the set up of your target, you may not have the
file /etc/protocols
So this fix may help in the file remote-utils.c
#if 0 //psw
protoent = getprotobyname ("tcp");
if (!protoent)
perror_with_name ("getprotobyname");
#endif
and a bit later
setsockopt (remote_desc,/* protoent->p_proto psw */ 6, TCP_NODELAY,
(char *) &tmp, sizeof (tmp));
You bypass the getprotobyname call and use the number 6.
Setjmp / longjmp
================
The first problem, after getting gdbserver to compile ( more later )
was the Illegal instruction error ( on the target system )
This could be a problem in ctr{oie}.o but I suspected setjmp.
# /bin/gdbserver
Illegal instruction
This was confirmed by putting
printf("before setjmp\n");
and
printf("after setjmp\n");
in the server.c code
The program output then became
# /bin/gdbserver
before setjmp
Illegal instruction
Here is the code change to server.c
...
int
main (int argc, char *argv[])
{
char ch, status, own_buf[PBUFSIZ], mem_buf[2000];
int i = 0;
unsigned char signal;
unsigned int len;
CORE_ADDR mem_addr;
printf(" before setjmp \n");
if (setjmp (toplevel))
{
fprintf (stderr, "Exiting\n");
exit (1);
}
printf(" after setjmp \n");
...
My memory did not fail me here I knew that
this is where setjmp is trying to save fp registers.
Here are some more clues..
arm-ml-elf-objdump -d
cache/build/uclibc-0.9.10/uclibc-0.9.10/libc/sysde ps/linux/arm/setjmp.o
file format elf32-littlearm
Disassembly of section .text:
00000000 <__sigsetjmp>:
0: eca0420c sfm f4, 4, [r0], #48 <-------Saving floats..
4: e8806ff0 stmia r0, {r4, r5, r6, r7, r8, r9, r10, r11, sp, lr}
8: e2400030 sub r0, r0, #48 ; 0x30
c: eafffffe b c <__sigsetjmp+0xc>
Here it is in the source code
cache/build/uclibc-0.9.10/uclibc-0.9.10/libc/sysdeps/linux/arm/setjmp.S
__sigsetjmp:
/* Save registers */
#ifdef __UCLIBC_HAS_FLOATS__
sfmea f4, 4, [r0]!
#endif
stmia r0, {v1-v6, sl, fp, sp, lr}
/* Restore pointer to jmp_buf */
sub r0, r0, #48
/* Make a tail call to __sigjmp_save; it takes the same args. */
B __sigjmp_save (PLT)
.size __sigsetjmp,.-__sigsetjmp;
The configuration for uClibc is contained in a Config file
cache/build/uclibc-0.9.10/uclibc-0.9.10/Config
which contains....
...
# Set this to `false' if you don't have/need basic floating point support
# support in libc (strtod, printf, scanf). Set it to `true' otherwise.
# If this is not true, then libm will not be built.
#HAS_FLOATING_POINT = true
HAS_FLOATING_POINT = false
This changed fixed it, here is a quick test...
rm cache/build/uclibc-0.9.10/uclibc-0.9.10/libc/sysde ps/linux/arm/setjmp.o
rm cache/build/uclibc-0.9.10/.configure
rm cache/build/uclibc-0.9.10/.compile
make clean
make
arm-ml-elf-objdump -d
cache/build/uclibc-0.9.10/uclibc-0.9.10/libc/sysdeps/linux/arm/setjmp.o
file format elf32-littlearm
Disassembly of section .text:
00000000 <__sigsetjmp>:
0: e8806ff0 stmia r0, {r4, r5, r6, r7, r8, r9, r10, r11, sp, lr}
4: e2400030 sub r0, r0, #48 ; 0x30
8: eafffffe b 8 <__sigsetjmp+0x8>
So do a make clean on uclibc and rebuild it all.
make -C cache/build/uclibc-0.9.10/uclibc-0.9.10/clean
rm cache/build/uclibc-0.9.10/.configure
rm cache/build/uclibc-0.9.10/.compile
make clean
make
This is a generic problem with ARM + sigjmp. We may need to think of a less
drastic solution.
Perhaps use a different flag in uclibc CPU_HAS_FP for example.
The new gdbserver ran like this
# /bin/gdbserver
before setjmp
after setjmp
Usage: gdbserver tty prog [args ...]
Exiting
#
The qOffsets message.
=======================
Now GDB server was starting but it did not reply to
some of the messages that gdb was sending it.
For XIP operation the qOffsets message is key.
This tells GDB where the target system actually loaded the code.
When you connect to a target system using the target remote
command ..
target remote 192.168.1.100:5000
Gdb will try to query the offsets. Most gdb servers do not respond to this
correctly.
Two files need to be modified to make this work.
In the kernel
linux/arch/armnommu/kernel/ptrace.c
needs to have a modification applied to it
to let it reply with the text and data ( and bss offsets )
Here are the modified parts of the file
case PTRACE_PEEKUSR:
printk("PEEKUser addr = %x ",addr);
ret = -EIO;
if ((addr & 3) || addr = sizeof(struct user)){
printk("invalid !\n ");
break;
}
#if 0
tmp = 0; /* Default return condition */
if (addr < sizeof(struct pt_regs))
tmp = get_stack_long(child, (int)addr >> 2);
#else
tmp = 0; /* Default return condition */
if (addr < sizeof(struct pt_regs)) {
tmp = get_stack_long(child, (int)addr >> 2);
} else if (addr == 4*49) {
tmp = child->mm->start_code;
} else if (addr == 4*50) {
tmp = child->mm->start_data;
} else if (addr == 4*51) {
tmp = child->mm->end_code;
// added this PSW
} else if (addr == 4*52) {
tmp = child->mm->end_data;
} else {
printk("error addr = %x (ignored)\n ",addr);
tmp = 0;
//break; //PSW removed break to allow extra requests
}
#endif
#if 0
printk(" value = %d max = %x\n ",
tmp,sizeof(struct pt_regs));
#endif
ret = put_user(tmp, (unsigned long *)data);
break;
There are two fixes here.
The addr == 4*49 code uses out of range
PEEKUSR addresses to trigger extraction of data related
to the task test and data load addresses.
The second fix was the trap to allow gdb server
to ask for more regs than struct pt_regs allows without failing the
ptrace call.
I have not tracked the problem down yet but
there was a mismatch in the number of registers in pt_regs that kernel uses
and those in the source for gdbserver.
The mod simply returns a zero value for the additional registers.
Note: This will only work if you patch gdbserver to
respond to the qOffsets message.
Here is the modified server.c
/* We are now stopped at the first instruction of the target process */
while (1)
{
remote_open (argv[1]);
restart:
setjmp (toplevel);
while (getpkt (own_buf) > 0)
{
unsigned char sig;
i = 0;
ch = own_buf[i++];
switch (ch)
{
case 'd':
remote_debug = !remote_debug;
break;
case '!':
extended_protocol = 1;
prepare_resume_reply (own_buf, status, signal);
break;
.... stuff missing
case 'M':
decode_M_packet (&own_buf[1], &mem_addr, &len, mem_buf);
printf(" Memory write len %d to addr %x\n",len,mem_addr);
if (write_inferior_memory (mem_addr, mem_buf, len) == 0)
write_ok (own_buf);
else
write_enn (own_buf);
break;
// added the q case ...PSW
case 'q':
switch (own_buf[1]) {
case 'C':
own_buf[0] = '\0';
break;
case 'O':
send_area(own_buf);
break;
default:
own_buf[0] = '\0';
break;
}
break;
case 'C':
convert_ascii_to_int (own_buf + 1, &sig, 1);
myresume (0, sig);
signal = mywait (&status);
prepare_resume_reply (own_buf, status, signal);
break;
...
This calls a new function send area
which needs to be added to low-linux.c
Notice this uses the new ptrace call ..
static void initialize_arch (void);
// PSW added this...
void send_area(char* buf) {
unsigned int x;
unsigned int code_start = ptrace (PTRACE_PEEKUSER, inferior_pid,
(PTRACE_ARG3_TYPE) 49*4, 0);
unsigned int data_start = ptrace (PTRACE_PEEKUSER, inferior_pid,
(PTRACE_ARG3_TYPE) 50*4, 0);
unsigned int bss_start = data_start;
unsigned int code_end = ptrace(PTRACE_PEEKUSER, inferior_pid,
(PTRACE_ARG3_TYPE) 51*4, 0);
unsigned int data_end = ptrace (PTRACE_PEEKUSER, inferior_pid,
(PTRACE_ARG3_TYPE) 52*4, 0);
printf("code at %p - %p, data at %p\n", code_start, code_end, data_start);
x = data_start - (code_end - code_start);
// this was Bernard's original I modified it but GDB
// ignores BSS anyway this needs to be fixed I think
//sprintf(buf,"Text=%x;Data=%x;Bss=%x;", code_start, x, x);
sprintf(buf,"Text=%x;Data=%x;Bss=%x;", code_start, data_start, data_end);
}
Well gdb may now be able to connect with the gdbserver.
Here is the output of a sample session
(gdb) target remote 192.168.1.100:5000
Sending packet: $Hc-1#09...Ack
Packet received: OK
Sending packet: $qC#b4...Ack
Packet received:
Sending packet: $qOffsets#4b...Ack
Packet received: Text=11e9520;Data=1570004;Bss=1571194;
Sending packet: $?#3f...Ack
Packet received: T050f:64951e01;0b:4c3f5701;0d:383f5701;
Sending packet: $Hg0#df...Ack
Packet received: OK
Sending packet: $g#67...Ack
Notice the qOffsets message and the Text Data Bss reply
This is looking hopeful.
But you may not be there yet. There is still more work to do on gdbserver.
get_inferior_registers
=======================
This is where gdbserver tries to get hold of the target program's registers.
You may have still not been able to compile gdbserver
due to some problems here.
Going back to low-linux.c there a place where
the target registers are defined.
The include file sys/reg.his missing in the arm architectures.
So you have to make up your own register definition
#ifdef HAVE_SYS_REG_H
//#include <sys/reg.h> // PSW commented this out the file
// is missing for the arm
#endif
You also have the U_REGS_OFFSET problem
( just define this as 0 )
#define U_REGS-OFFSET 0 // Hack to prevent an invalid PTRACE call
/* U_REGS_OFFSET is the offset of the registers within the u area. */
You now need to add an arm registers definition to
low-linux.c
This is not the ideal way but it will work for now
#define ARM_GNULINUX_TARGET 1
#ifdef I386_GNULINUX_TARGET
/*********************** I386_GNULINUX_TARGET ******************/
/* This module only supports access to the general purpose registers.
Adjust the relevant constants accordingly.
....
....
int
m68k_linux_register_u_addr (int blockend, int regnum)
{
return (blockend + 4 * regmap[regnum]);
}
// start of the arm definition
#elif defined(ARM_GNULINUX_TARGET)
/*********************** ARM_GNULINUX_TARGET ******************/
// not too sure on this yet but it seemed to work
static int my_regmap[] =
{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16,17,18
};
static void
initialize_arch (void)
{
return;
}
// special routine to get the PTRACE address
int
arm_linux_register_u_addr (int blockend, int regnum)
{
int iret = blockend + 4 * my_regmap[regnum];
return iret;
}
#if !defined (REGISTER_U_ADDR)
#define REGISTER_U_ADDR(addr,blockend,regno) \
addr = arm_linux_register_u_addr (blockend, regno)
#endif
#elif defined(IA64_GNULINUX_TARGET)
/*********************** IA64_GNULINUX_TARGET ******************/
...
...
This has two effects.
- the registers are returned with correct values
- hopefully gdbserver will now compile.
The connect to the target will also have some good register information
in it.
Looking again at the gdb target remote debug output
...
Sending packet: $qOffsets#4b...Ack
Packet received: Text=11e9520;Data=1570004;Bss=1571194;
Sending packet: $?#3f...Ack
Packet received: T050f:64951e01;0b:4c3f5701;0d:383f5701;
Sending packet: $Hg0#df...Ack
Packet received: OK
Sending packet: $g#67...Ack
Packet received: e4005701620000001c3f57011c3f570162000000100000006c 3f570100
0000000000000000000000040057014c3f57011c3f5701383f 57019c9e
1e0164951e0110000000e400570100000000e4005701000000 00000000
00000000000000000000000000000000000000000000000000 00000000
00000000000000000000000000000000000000000000000000 00000000
0000000000000000000000000000000000000000000000
Sending packet: $m11e9534,4#69...Ack
Packet received: 580000eb
0x11e9564 in main (argc=22479076, argv=0x62) at hello.c:12
(gdb)
The key here is the fact that the response to the
$g#67...Ack contained all the register contents and we
managed to find out where the target code had stopped .
Now you will still not be able to set breakpoints yet.
Breakpoints
=============
The breakpoint for the ARM7TDMI is different from those used for other Arm
systems.
The other systems use an Illegal Instruction.
(It would be a good learning
project to add the correct trap for this to uClinux.)
uClinux uses a different breakpoint instruction.
You have to modify the behavior of gdb now to reflect the different
instruction.
The file
gdb-5.x/gdb/config/arm/tm-arm.h contains the following code
these are incorrect for our ARM7 uClinux system
#define ARM_LE_BREAKPOINT {0xFE,0xDE,0xFF,0xE7}
#define ARM_BE_BREAKPOINT {0xE7,0xFF,0xDE,0xFE}
#define THUMB_LE_BREAKPOINT {0xfe,0xdf}
#define THUMB_BE_BREAKPOINT {0xdf,0xfe}
It depends on how the whole thing is configured but sometimes this file
is overridden by the file
gdb-5.x/gdb/config/arm/tm-linux.h
Here the additions will override the previous definitions.
#undef ARM_LE_BREAKPOINT
#define ARM_LE_BREAKPOINT {0x01,0x00,0x9f,0xef}
#undef ARM_BE_BREAKPOINT
#define ARM_BE_BREAKPOINT {0xef,0x95,0x00,0x01}
If, for some reason, your configuration of GDB did not include
tm-linux.h then put the overrides in both files.
Another problem with gdb ( and this is from Bernhard's notes )
Is the fact that in file
gdb-5.x/gdb/arm-tdep.c the
read_fp() call needs to be modified.
I have not yet seen where this was a problem.
/* Return the frame address. On ARM, it is R11; on Thumb it is R7.
Examine the Program Status Register to decide which state we're in. */
CORE_ADDR
arm_target_read_fp (void)
{
if (read_register (PS_REGNUM) & 0x20) /* Bit 5 is Thumb state bit */
return read_register (THUMB_FP_REGNUM); /* R7 if Thumb */
else { //PSW
CORE_ADDR a;
// I have seen two definitions ARM_FP_REGNUM and (FP_REGNUM)
//a=read_register (ARM_FP_REGNUM); /* R11 if ARM */
a=read_register (FP_REGNUM); /* R11 if ARM */
if(a) return a;
else return read_sp(); /* if fp is not initialized, use sp as fp */
}
}
libg.a
========
If this library is missing when you try top compile something
just copy libc.a to libg.a
There is a switch in the compiler specs that causes
the -g flag to use libg.a and not libc.a
Elf file format
================
The last problem was with the Midori toolchain ld script.
When trying gdb on the xxx.gdb file
I got this..
./gdb hello.gdb
GNU gdb 5.0
Copyright 2000 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "--host=i686-pc-linux-gnu --target=arm-elf"
...Dwarf Error: bad offset (0x2334) in compilation unit header
(offset 0x0 + 6).
This is caused by the stabs section missing from the
elf2flt.ld file.
The additions are shown
... stuff missing.
.junk : {
*(.rel*)
*(.rela*)
} > junk
/* Stabs debugging sections. */
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment 0 : { *(.comment) }
.debug_abbrev 0 : { *(.debug_abbrev) }
.debug_info 0 : { *(.debug_info) }
.debug_line 0 : { *(.debug_line) }
.debug_pubnames 0 : { *(.debug_pubnames) }
.debug_aranges 0 : { *(.debug_aranges) }
}
In my example I had to create the new elf2flt.ld file
this can exist in the same dir as the file you are compiling or
you place it ( in my example ) in
cache/tools/lib/gcc-lib/arm-ml-elf/2.95.3/elf2flt.ld
Other patches
===============
In looking through Bernhard Kuhn's modifications I see the following
patches.
+++ arm-elf-gdb-5.2.1/gdb/config/arm/tm-embed.h Fri Sep 20 00:58:05 2002
@@ -27,7 +27,7 @@
/* The remote stub should be able to single-step. */
#undef SOFTWARE_SINGLE_STEP_P
-#define SOFTWARE_SINGLE_STEP_P() 0
+#define SOFTWARE_SINGLE_STEP_P() 1
/* The first 0x20 bytes are the trap vectors. */
#undef LOWEST_PC
Conclusion
===========
This was a complex problem.
It required debugging in all aspects of the system
- Kernel
- Cross Tools ld scripts
- Libraries
- GDB host
- gdbserver
At all times the path to the solution was complicated by multiple
dependencies.
I am not sure if I have the whole problem solved even yet. But I have a
good handle on how to fix it now.
I am quite experienced at working through Linux problems but this
took me over 40 hours to solve. There are some twists and turns that, I feel,
would have made many give up.
Even now there are issues left to fix I will get round to them
as I have time.
Phil Wilshire.
|