OK, I think I figured it out. This is a sort of bug I haven't seen before, and it may be the first member of a whole class of bugs. What's going on is that I use code from the intel port which expects a pass-arguments-in-registers convention supported by gdb. Unfortunately, I turn off (as a side-effect of something else) any use of that convention in UML. So, in include/asm-i386/rwsem.h we have: extern struct rw_semaphore *FASTCALL(rwsem_down_write_failed(struct rw_semaphore *sem)); and LOCK_PREFIX " xadd %0,(%%eax)\n\t" /* subtract 0x0000ffff, returns the old value */ -------snip snip------------ " pushl %%ecx\n\t" " call rwsem_down_write_failed\n\t" " popl %%ecx\n\t" This puts the semaphore address in %eax (the first line) and saves %ecx on the stack because rwsem_down_write_failed will use it (I guess). FASTCALL is defined in include/linux/kernel.h as __attribute((regparam(3))) if __i386__ is defined and nothing otherwise. Well, I turn off __i386__ in the UML build. So, rwsem_down_write_failed is compiled as a normal procedure which gets its arguments on the stack. The sequence above "saved" %ecx on the stack, but rwsem_down_write_failed looks at that value as its argument rather than %eax because it was miscompiled. %ecx happens to contain a userspace address which had just been unmapped, so UML segfaults when it tries to use it as a semaphore. Once that happens, it goes into a recursive segfault because the first thing the segfault handler tries to do is grab that same semaphore. So, it goes spiralling down the stack until it hits the guard page, at which point it gets killed by the host kernel because there's nowhere to put the signal frame. So, the UML tracing thread sees a process getting killed out of the blue.