Repository: google/gvisor
File: pkg/sentry/platform/systrap/usertrap/usertrap_amd64.go (Go)

Code before:
	if err != nil {
		return err
	}

	return nil
}

// PatchSyscall changes the syscall instruction into a function call.
func (s *State) PatchSyscall(ctx context.Context, ac *arch.Context64, mm memoryManager) error {
	task := kernel.TaskFromContext(ctx)
	if task == nil {
		return fmt.Errorf("no task found")
	}

	s.mu.Lock()
	defer s.mu.Unlock()

	sysno := ac.SyscallNo()
	patchAddr := ac.IP() - uintptr(len(jmpInst))

	prevCode := make([]uint8, len(jmpInst))
	if _, err := primitive.CopyUint8SliceIn(task.OwnCopyContext(usermem.IOOpts{AddressSpaceActive: false}), hostarch.Addr(patchAddr), prevCode); err != nil {
		return err
	}

	// Check that another thread has not patched this syscall yet.
	// 0xb8 is the first byte of "mov sysno, %eax".
	if prevCode[0] == uint8(0xb8) {
		ctx.Debugf("Found the pattern at ip %x:sysno %d", patchAddr, sysno)

		trapAddr, err := s.addTrapLocked(ctx, ac, mm, uint32(sysno))
		if trapAddr == 0 || err != nil {
			ctx.Warningf("Failed to add a new trap: %v", err)
			return nil
		}

		// Replace "mov sysno, %eax; syscall" with "jmp trapAddr".
		newCode := make([]uint8, len(jmpInst))
		copy(newCode[:jmpInstOpcodeLen], jmpInst[:jmpInstOpcodeLen])
		binary.LittleEndian.PutUint32(newCode[jmpInstOpcodeLen:], uint32(trapAddr))

		ctx.Debugf("Apply the binary patch addr %x trap addr %x (%v -> %v)", patchAddr, trapAddr, prevCode, newCode)

		ignorePermContext := task.OwnCopyContext(usermem.IOOpts{IgnorePermissions: true})

		// The patch can't be applied atomically, so we need to
		// guarantee that in each moment other threads will read a
		// valid set of instructions, detect any inconsistent states
		// and restart the patched code if so.
		//
		// A subtle aspect is the address at which the user trap table

Review comment (performance):
Without a sync.Once this will produce a bit of log spam, but I think this might actually be desirable because if the user is using a tracer they likely no longer care about performance loss from overprinting, and repeated messages will get the point across that this isn't a great thing to do on Systrap until we fix it. 

Code after:
	if err != nil {
		return err
	}

	return nil
}

// PatchSyscall changes the syscall instruction into a function call.
func (s *State) PatchSyscall(ctx context.Context, ac *arch.Context64, mm memoryManager) error {
	task := kernel.TaskFromContext(ctx)
	if task == nil {
		return fmt.Errorf("no task found")
	}

	// Skip syscall patching when the task is being ptraced, because
	// single-stepping and other debugger features are incompatible with
	// the "syshandler" routine used to handle patched syscalls (see
	// syshandler_amd64.S). This incompatibility can result in inconsistent
	// process states and failures (e.g. SIGSEGV).
	// TODO(gvisor.dev/issue/11649): for a full fix we'd need to roll back
	//     existing patched syscalls, in case the traced program was patched
	//     before being traced (e.g. PTRACE_ATTACH on an already running
	//     process).
	if task.Tracer() != nil {
		if s.nextTrap > 0 {
			ctx.Warningf("LIKELY ERROR: Attached tracer to process with patched syscalls (traps %d)! Systrap is not fully compatible with ptrace/debuggers, program may die unexpectedly soon!", s.nextTrap)
		}
		return nil
	}

	s.mu.Lock()
	defer s.mu.Unlock()

	sysno := ac.SyscallNo()
	patchAddr := ac.IP() - uintptr(len(jmpInst))

	prevCode := make([]uint8, len(jmpInst))
	if _, err := primitive.CopyUint8SliceIn(task.OwnCopyContext(usermem.IOOpts{AddressSpaceActive: false}), hostarch.Addr(patchAddr), prevCode); err != nil {
		return err
	}

	// Check that another thread has not patched this syscall yet.
	// 0xb8 is the first byte of "mov sysno, %eax".
	if prevCode[0] == uint8(0xb8) {
		ctx.Debugf("Found the pattern at ip %x:sysno %d", patchAddr, sysno)

		trapAddr, err := s.addTrapLocked(ctx, ac, mm, uint32(sysno))
		if trapAddr == 0 || err != nil {
			ctx.Warningf("Failed to add a new trap: %v", err)
			return nil
		}