/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8     -*-│
│vi: set et ft=asm ts=8 tw=8 fenc=utf-8                                     :vi│
╞══════════════════════════════════════════════════════════════════════════════╡
│ Copyright 2021 Justine Alexandra Roberts Tunney                              │
│                                                                              │
│ Permission to use, copy, modify, and/or distribute this software for         │
│ any purpose with or without fee is hereby granted, provided that the         │
│ above copyright notice and this permission notice appear in all copies.      │
│                                                                              │
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL                │
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED                │
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE             │
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL         │
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR        │
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER               │
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR             │
│ PERFORMANCE OF THIS SOFTWARE.                                                │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/dce.h"
#include "libc/macros.internal.h"
#include "libc/sysv/consts/auxv.h"
#include "libc/sysv/consts/prot.h"
.privileged

//	Opens executable in O_RDWR mode.
//
//	To avoid ETXTBSY we need to unmap the running executable first,
//	then open the file, and finally load the code back into memory.
//
//	@return	file descriptor
//	@note	only works on .com binary (not .com.dbg)
//	@note	only supports linux, freebsd, openbsd, and netbsd
OpenExecutable:
	push	%rbp
	mov	%rsp,%rbp
	pushq	__NR_open(%rip)			# -0x08(%rbp)
	pushq	__NR_mmap(%rip)			# -0x10(%rbp)
	pushq	__NR_munmap(%rip)		# -0x18(%rbp)
	pushq	O_RDWR(%rip)			# -0x20(%rbp)
	pushq	MAP_ANONYMOUS(%rip)		# -0x28(%rbp)
	pushq	MAP_PRIVATE(%rip)		# -0x30(%rbp)
	pushq	MAP_FIXED(%rip)			# -0x38(%rbp)
	pushq	__NR_mprotect(%rip)		# -0x40(%rbp)
	push	%rbx				# code buffer
	push	%r12				# data buffer
	push	%r14				# filename
	push	%r15				# fd

//	Get filename.
	lea	program_executable_name(%rip),%r14

//	Allocate code buffer.
	mov	-0x10(%rbp),%eax		# __NR_mmap
	xor	%edi,%edi
	mov	$PAGESIZE,%esi
	mov	$PROT_READ|PROT_WRITE,%edx
	mov	-0x28(%rbp),%r10d		# MAP_ANONYMOUS
	or	-0x30(%rbp),%r10d		# MAP_PRIVATE
	mov	$-1,%r8
	mov	$0,%r9
	push	%r9				# openbsd:pad
	push	%r9				# openbsd:align
	syscall
	pop	%r9
	pop	%r9
	mov	%rax,%rbx

//	Allocate data buffer.
	mov	-0x10(%rbp),%eax		# __NR_mmap
	xor	%edi,%edi
	mov	$ape_ram_filesz,%esi
	mov	$PROT_READ|PROT_WRITE,%edx
	mov	-0x28(%rbp),%r10d		# MAP_ANONYMOUS
	or	-0x30(%rbp),%r10d		# MAP_PRIVATE
	mov	$-1,%r8
	mov	$0,%r9
	push	%r9				# openbsd:pad
	push	%r9				# openbsd:align
	syscall
	pop	%r9
	pop	%r9
	mov	%rax,%r12

//	Move data.
	mov	%r12,%rdi
	mov	$ape_ram_vaddr,%esi
	mov	$ape_ram_filesz,%ecx
	rep movsb

//	Move code.
	mov	%rbx,%rdi
	mov	$8f,%esi
	mov	$9f-8f,%ecx
	rep movsb

//	Change protection.
	mov	-0x40(%rbp),%eax		# __NR_mprotect
	mov	%rbx,%rdi
	mov	$PAGESIZE,%esi
	mov	$PROT_READ|PROT_EXEC,%edx
	syscall

	jmp	*%rbx

//	<LIMBO>

//	Unmap code segment.
8:	mov	-0x18(%rbp),%eax		# __NR_munmap
	mov	$ape_rom_vaddr,%edi
	mov	$ape_rom_filesz,%esi
	syscall

//	Unmap data segment.
	mov	-0x18(%rbp),%eax		# __NR_munmap
	mov	$ape_ram_vaddr,%edi
	mov	$ape_ram_filesz,%esi
	syscall

//	Open executable in read-write mode.
	mov	-0x08(%rbp),%eax		# __NR_open
	mov	%r14,%rdi
	mov	-0x20(%rbp),%esi		# O_RDWR
	syscall
	mov	%eax,%r15d

//	Map code segment.
	mov	-0x10(%rbp),%eax		# __NR_mmap
	mov	$ape_rom_vaddr,%edi
	mov	$ape_rom_filesz,%esi
	mov	$PROT_READ|PROT_EXEC,%edx
	mov	-0x38(%rbp),%r10d		# MAP_FIXED
	or	-0x30(%rbp),%r10d		# MAP_PRIVATE
	mov	%r15d,%r8d
	mov	$ape_rom_offset,%r9d
	push	%r9				# openbsd:pad
	push	%r9				# openbsd:align
	syscall
	pop	%r9
	pop	%r9

//	Allocate data segment.
	mov	-0x10(%rbp),%eax		# __NR_mmap
	mov	$ape_ram_vaddr,%edi
	mov	$ape_ram_filesz,%esi
	mov	$PROT_READ|PROT_WRITE,%edx
	mov	-0x38(%rbp),%r10d		# MAP_FIXED
	or	-0x30(%rbp),%r10d		# MAP_PRIVATE
	or	-0x28(%rbp),%r10d		# MAP_ANONYMOUS
	mov	$-1,%r8
	mov	$0,%r9
	push	%r9				# openbsd:pad
	push	%r9				# openbsd:align
	syscall
	pop	%r9
	pop	%r9

//	Put data back.
	mov	$ape_ram_vaddr,%edi
	mov	%r12,%rsi
	mov	$ape_ram_filesz,%ecx
	rep movsb

//	Jump back.
	mov	$9f,%eax
	jmp	*%rax

//	</LIMBO>

//	Deallocate code buffer.
9:	mov	__NR_munmap,%eax
	mov	%rbx,%rdi
	mov	$PAGESIZE,%esi
	syscall

//	Deallocate data buffer.
	mov	__NR_munmap,%eax
	mov	%r12,%rdi
	mov	$ape_ram_filesz,%esi
	syscall

	mov	%r15d,%eax
	pop	%r15
	pop	%r14
	pop	%r12
	pop	%rbx
	leave
	ret
9:	.endfn	OpenExecutable,globl

	.weak	ape_rom_vaddr
	.weak	ape_rom_filesz
	.weak	ape_rom_offset
	.weak	ape_ram_vaddr
	.weak	ape_ram_filesz