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
|
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]
|
--os [os] cross-compile for OS: [linux|mac|windows]
|
||||||
--verbose, -v show everything
|
--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
|
help show this help
|
|
@ -3,10 +3,10 @@ package cli
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
|
|
||||||
"git.urbach.dev/cli/q/src/compiler"
|
"git.urbach.dev/cli/q/src/compiler"
|
||||||
"git.urbach.dev/cli/q/src/linker"
|
"git.urbach.dev/cli/q/src/linker"
|
||||||
|
"git.urbach.dev/cli/q/src/memfile"
|
||||||
)
|
)
|
||||||
|
|
||||||
// run builds and runs the executable.
|
// run builds and runs the executable.
|
||||||
|
@ -17,23 +17,20 @@ func run(args []string) int {
|
||||||
return exit(err)
|
return exit(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := compiler.Compile(b)
|
env, err := compiler.Compile(b)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return exit(err)
|
return exit(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = linker.WriteFile(b.Executable(), b, result)
|
file, err := memfile.New(b.Executable())
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return exit(err)
|
return exit(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(b.Executable())
|
linker.Write(file, b, env)
|
||||||
cmd.Stdout = os.Stdout
|
err = memfile.Exec(file)
|
||||||
cmd.Stdin = os.Stdin
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
err = cmd.Run()
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
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 (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"git.urbach.dev/cli/q/src/asm"
|
|
||||||
"git.urbach.dev/cli/q/src/build"
|
"git.urbach.dev/cli/q/src/build"
|
||||||
"git.urbach.dev/cli/q/src/core"
|
"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.
|
// WriteFile writes an executable file to disk.
|
||||||
|
@ -20,36 +15,12 @@ func WriteFile(executable string, b *build.Build, env *core.Environment) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
init := env.Functions["run.init"]
|
Write(file, b, env)
|
||||||
traversed := make(map[*core.Function]bool, len(env.Functions))
|
err = file.Chmod(0o755)
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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