From: pk@cs.few.eur.nl (Paul Kranenburg)
Subject: Re: GDB under 386bsd 0.1
Keywords: gdb, ptrace
Reply-To: pk@cs.eur.nl
Organization: Erasmus University Rotterdam
Date: Fri, 4 Sep 1992 15:35:54 GMT

In <1992Sep4.005417.3876@gumby.dsd.trw.com> gottloeb@eel.dsd.trw.com writes:

>I have noticed a problem using gdb under 386bsd 0.1.

>When at least one breakpoint has been set and the program terminates,
>e.g. executes exit(), the breakpoint is not removed from the process's
>text image.  When the program is subsequently re-executed under gdb,
>gdb remembers that a breakpoint is supposed to at the memory location
>and puts one there again.  However this time it shadows the breakpoint
>instruction from the previous run rather than the original instruction.
>When execution begins after reaching the breakpoint, various traps occur.

>If the program is run after quitting gdb, a Trace/BPT trap occurs.

>I think the problem is that gdb assumes that when ptrace modifies the
>process's image the kernel either makes a private copy of the text image
>or it will throw away the text image after the process terminates.
>However, this is not the case - the kernel keeps the modified text image
>around and executes it rather than a fresh copy from the original file.

The 386 does not generate a page protection fault while it is executing
in supervisor mode :-(, so copy on write handling never takes place when
the kernel stuffs data into a process's text- or any other non-anonymous
segment. So these cases must be explicitly checked for. Here's a patch
for kern/sys_process.c:


------- sys_process.c -------
*** /tmp/da05975	Fri Sep  4 16:51:50 1992
--- sys_process.c	Fri Sep  4 16:51:18 1992
***************
*** 278,286 ****
  		break;
  
  	case PT_WRITE_I:
! 	case PT_WRITE_D:
! 		ipc.error = copyout((char *)&ipc.data, (char *)ipc.addr, sizeof(ipc.data));
  		break;
  
  	case PT_WRITE_U:
  		if ((u_int)ipc.addr > UPAGES * NBPG - sizeof(int)) {
--- 278,318 ----
  		break;
  
  	case PT_WRITE_I:
! 	case PT_WRITE_D: {
! 		vm_prot_t prot;		/* current protection of region */
! 		int cow;		/* ensure copy-on-write happens */
! 
! 		if (cow = (useracc(ipc.addr, sizeof(ipc.data), B_WRITE) == 0)) {
! 			vm_offset_t	addr = (vm_offset_t)ipc.addr;
! 			vm_size_t	size;
! 			vm_prot_t	max_prot;
! 			vm_inherit_t	inh;
! 			boolean_t	shared;
! 			vm_object_t	object;
! 			vm_offset_t	objoff;
! 
! 			if (vm_region(&p->p_vmspace->vm_map, &addr, &size,
! 					&prot, &max_prot, &inh, &shared,
! 					&object, &objoff) != KERN_SUCCESS ||
! 			    vm_protect(&p->p_vmspace->vm_map, ipc.addr,
! 					sizeof(ipc.data), FALSE,
! 					prot|VM_PROT_WRITE) != KERN_SUCCESS ||
! 			    vm_fault(&p->p_vmspace->vm_map,trunc_page(ipc.addr),
! 					VM_PROT_WRITE, FALSE) != KERN_SUCCESS) {
! 
! 				ipc.error = EFAULT;
! 				break;
! 			}
! 		}
! 		ipc.error = copyout((char *)&ipc.data,
! 					(char *)ipc.addr, sizeof(ipc.data));
! 		if (cow)
! 			if (vm_protect(&p->p_vmspace->vm_map, ipc.addr,
! 					sizeof(ipc.data), FALSE,
! 					prot) != KERN_SUCCESS)
! 				printf("ptrace: oops\n");
  		break;
+ 	}
  
  	case PT_WRITE_U:
  		if ((u_int)ipc.addr > UPAGES * NBPG - sizeof(int)) {
------------ EOP --------------------

I am not entirely happy about the call to `vm_fault', but at this moment 
I don't see another way to enforce a copy on write.

-pk