uCdot
search uCdot:
 
Embedded Linux and uClinux Developer Forum
 
uCdot
- FAQ
- Dev Boards
- Submit Story
- Submit FAQ
- Submit Dev Board
- Topics
- Authors
- About

- Preferences
- Older Stuff
- Past Polls
- Discussions
- Journals
- Messages

Embedded Linux
Mailing Lists
uClinux-dev (search)
Coldfire (search)
MTD
Microblaze (search)
ELUG
BDM-devel
Blackfin

Embedded Linux
Sites
uClinux.org
uClinux-Dist
uClibc
uClinux Directory
LinuxDevices
ARMulator
uClinux-elf-tools
Colilo
Kernel Archives
H8-uClinux
TLDP
Microblaze uClinux
BDM Tools
SkyEye (emulator)
LOM
SETR live CD
Blackfin uClinux

Embedded Linux
Companies
SecureComputing
SDCS
CodePoet
Arcturus
Cadenux
ARMtwister
uClinux.net
Xiptech
senTec
embedded^cl
Cwlinux
emlix
TimeSys
eSpark Infotech
SSV Embedded Systems
Embedded Minds
PeerSec Networks
Vortech Consulting
swissEmbedded
Synertronixx
Mbedthis Software
.vantronix
Aday
GraceLabs
Pengutronix
metux ITS
Codito Technologies
Firmix Software
PetaLogix
NuDesign
Merritt Technologies
WindRiver
OpenGear
Rubico
Analog Devices
Artila Electronics
Vyatta
Embest Info&Tech
Katalix Systems
WorkWare Systems
Kdev
Intellimetrix
Virtual Cogs
SYSGO
coresystems
ExactCODE
KOAN
EzHomeTech
Linux4biz
Linkodas
Trego
EMBEST
Boardcon
HITEG
FemtoLinux
Prosoft World
Witech

 
Case Study: Gdbserver on ARM7 Platforms
FAQ

This a collection of notes on how to get the arm version of GDB server working. This may be somewhat wordy but I have tried to capture the path I took to get the system running.

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.

uClinux-2.5.52-uc0 released | uClinux Training Munich Jan 28-31 2003  >

 

 
uCdot Login
Nickname:

Password:

[ Create a new account ]

Related Links
  • uClibc
  • Linux
  • uClinux
  • Phil Wilshire
  • More on FAQ
  • Also by davidm
  • This discussion has been archived. No new comments can be posted.
    Case Study: Gdbserver on ARM7 Platforms | Login/Create an Account | Top | Search Discussion
    Threshold:
    The Fine Print: The following comments are owned by whoever posted them. We are not responsible for them in any way.

    The Embedded Linux and uClinux Developer Forum is hosted by: SnapGear A celebrity is a person who is known for his well-knownness.

    [ home | contribute story | older articles | past polls | faq | authors | preferences ]