Implemented running executables in memory
All checks were successful
/ test (push) Successful in 16s
All checks were successful
/ test (push) Successful in 16s
This commit is contained in:
parent
e5aa006623
commit
5d39a13181
10 changed files with 175 additions and 42 deletions
2
go.mod
2
go.mod
|
@ -7,4 +7,4 @@ require (
|
|||
git.urbach.dev/go/color v0.0.0-20250606151219-222306e0b534
|
||||
)
|
||||
|
||||
require golang.org/x/sys v0.33.0 // indirect
|
||||
require golang.org/x/sys v0.33.0
|
|
@ -9,5 +9,5 @@ Commands:
|
|||
--os [os] cross-compile for OS: [linux|mac|windows]
|
||||
--verbose, -v show everything
|
||||
|
||||
run [directory | file] build and run the executable
|
||||
run [directory | file] build and run the executable in memory
|
||||
help show this help
|
|
@ -3,10 +3,10 @@ package cli
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"git.urbach.dev/cli/q/src/compiler"
|
||||
"git.urbach.dev/cli/q/src/linker"
|
||||
"git.urbach.dev/cli/q/src/memfile"
|
||||
)
|
||||
|
||||
// run builds and runs the executable.
|
||||
|
@ -17,23 +17,20 @@ func run(args []string) int {
|
|||
return exit(err)
|
||||
}
|
||||
|
||||
result, err := compiler.Compile(b)
|
||||
env, err := compiler.Compile(b)
|
||||
|
||||
if err != nil {
|
||||
return exit(err)
|
||||
}
|
||||
|
||||
err = linker.WriteFile(b.Executable(), b, result)
|
||||
file, err := memfile.New(b.Executable())
|
||||
|
||||
if err != nil {
|
||||
return exit(err)
|
||||
}
|
||||
|
||||
cmd := exec.Command(b.Executable())
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stderr = os.Stderr
|
||||
err = cmd.Run()
|
||||
linker.Write(file, b, env)
|
||||
err = memfile.Exec(file)
|
||||
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
|
|
41
src/linker/Write.go
Normal file
41
src/linker/Write.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
package linker
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"git.urbach.dev/cli/q/src/asm"
|
||||
"git.urbach.dev/cli/q/src/build"
|
||||
"git.urbach.dev/cli/q/src/core"
|
||||
"git.urbach.dev/cli/q/src/data"
|
||||
"git.urbach.dev/cli/q/src/elf"
|
||||
"git.urbach.dev/cli/q/src/macho"
|
||||
"git.urbach.dev/cli/q/src/pe"
|
||||
)
|
||||
|
||||
// Write writes an executable to the given writer.
|
||||
func Write(writer io.WriteSeeker, b *build.Build, env *core.Environment) {
|
||||
program := asm.Assembler{
|
||||
Instructions: make([]asm.Instruction, 0, 32),
|
||||
Data: make(data.Data, 32),
|
||||
}
|
||||
|
||||
init := env.Functions["run.init"]
|
||||
traversed := make(map[*core.Function]bool, len(env.Functions))
|
||||
|
||||
// This will place the init function immediately after the entry point
|
||||
// and also add everything the init function calls recursively.
|
||||
init.EachDependency(traversed, func(f *core.Function) {
|
||||
program.Merge(&f.Assembler)
|
||||
})
|
||||
|
||||
code, data, libs := program.Compile(b)
|
||||
|
||||
switch b.OS {
|
||||
case build.Linux:
|
||||
elf.Write(writer, b, code, data)
|
||||
case build.Mac:
|
||||
macho.Write(writer, b, code, data)
|
||||
case build.Windows:
|
||||
pe.Write(writer, b, code, data, libs)
|
||||
}
|
||||
}
|
|
@ -3,13 +3,8 @@ package linker
|
|||
import (
|
||||
"os"
|
||||
|
||||
"git.urbach.dev/cli/q/src/asm"
|
||||
"git.urbach.dev/cli/q/src/build"
|
||||
"git.urbach.dev/cli/q/src/core"
|
||||
"git.urbach.dev/cli/q/src/data"
|
||||
"git.urbach.dev/cli/q/src/elf"
|
||||
"git.urbach.dev/cli/q/src/macho"
|
||||
"git.urbach.dev/cli/q/src/pe"
|
||||
)
|
||||
|
||||
// WriteFile writes an executable file to disk.
|
||||
|
@ -20,36 +15,12 @@ func WriteFile(executable string, b *build.Build, env *core.Environment) error {
|
|||
return err
|
||||
}
|
||||
|
||||
init := env.Functions["run.init"]
|
||||
traversed := make(map[*core.Function]bool, len(env.Functions))
|
||||
|
||||
final := asm.Assembler{
|
||||
Instructions: make([]asm.Instruction, 0, 32),
|
||||
Data: make(data.Data, 32),
|
||||
}
|
||||
|
||||
// This will place the main function immediately after the entry point
|
||||
// and also add everything the main function calls recursively.
|
||||
init.EachDependency(traversed, func(f *core.Function) {
|
||||
final.Merge(&f.Assembler)
|
||||
})
|
||||
|
||||
code, data, libs := final.Compile(b)
|
||||
|
||||
switch b.OS {
|
||||
case build.Linux:
|
||||
elf.Write(file, b, code, data)
|
||||
case build.Mac:
|
||||
macho.Write(file, b, code, data)
|
||||
case build.Windows:
|
||||
pe.Write(file, b, code, data, libs)
|
||||
}
|
||||
|
||||
err = file.Close()
|
||||
Write(file, b, env)
|
||||
err = file.Chmod(0o755)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.Chmod(executable, 0o755)
|
||||
return file.Close()
|
||||
}
|
47
src/memfile/Exec_linux.go
Normal file
47
src/memfile/Exec_linux.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
//go:build linux
|
||||
|
||||
package memfile
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Exec executes an in-memory file.
|
||||
func Exec(file *os.File) error {
|
||||
empty, err := syscall.BytePtrFromString("")
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
argv := []string{"/proc/self/fd/" + strconv.Itoa(int(file.Fd()))}
|
||||
argvp, err := syscall.SlicePtrFromStrings(argv)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
envv := os.Environ()
|
||||
envvp, err := syscall.SlicePtrFromStrings(envv)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _, errno := syscall.Syscall6(
|
||||
unix.SYS_EXECVEAT,
|
||||
uintptr(file.Fd()),
|
||||
uintptr(unsafe.Pointer(empty)),
|
||||
uintptr(unsafe.Pointer(&argvp[0])),
|
||||
uintptr(unsafe.Pointer(&envvp[0])),
|
||||
uintptr(unix.AT_EMPTY_PATH),
|
||||
0,
|
||||
)
|
||||
|
||||
return errno
|
||||
}
|
29
src/memfile/Exec_other.go
Normal file
29
src/memfile/Exec_other.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
//go:build !linux
|
||||
|
||||
package memfile
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// Exec executes an in-memory file.
|
||||
func Exec(file *os.File) error {
|
||||
err := file.Chmod(0o700)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = file.Close()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := exec.Command(file.Name())
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
21
src/memfile/New_linux.go
Normal file
21
src/memfile/New_linux.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
//go:build linux
|
||||
|
||||
package memfile
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// New creates a new anonymous in-memory file.
|
||||
func New(name string) (*os.File, error) {
|
||||
fd, err := unix.MemfdCreate(name, unix.MFD_CLOEXEC)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
memFile := os.NewFile(uintptr(fd), name)
|
||||
return memFile, nil
|
||||
}
|
12
src/memfile/New_other.go
Normal file
12
src/memfile/New_other.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
//go:build !linux
|
||||
|
||||
package memfile
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// New creates a new anonymous in-memory file.
|
||||
func New(name string) (*os.File, error) {
|
||||
return os.CreateTemp("", "")
|
||||
}
|
15
src/memfile/memfile_test.go
Normal file
15
src/memfile/memfile_test.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
package memfile_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.urbach.dev/cli/q/src/memfile"
|
||||
"git.urbach.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
file, err := memfile.New("")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, file)
|
||||
// memfile.Exec can't be tested because it would replace the test executable
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue