This commit is contained in:
parent
dbc865ee67
commit
12e0ccf7b2
36 changed files with 738 additions and 42 deletions
27
docs/pe.md
Normal file
27
docs/pe.md
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
# Portable Executable
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
Unlike Linux, Windows does not ignore zero-length sections at the end of a file and will
|
||||||
|
fail loading them because they don't exist within the file. Adding a single byte to the
|
||||||
|
section can fix this problem, but it's easier to just remove the section header entirely.
|
||||||
|
The solution used here is to guarantee that the data section is never empty by always
|
||||||
|
importing a few core functions from "kernel32.dll".
|
||||||
|
|
||||||
|
## DLL function pointers
|
||||||
|
|
||||||
|
The section where the DLL function pointers are stored does not need to be marked as writable.
|
||||||
|
The Windows executable loader resolves the pointers before they are loaded into memory.
|
||||||
|
|
||||||
|
The stack must be 16 byte aligned before a DLL function is called.
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
- https://learn.microsoft.com/en-us/windows/win32/debug/pe-format
|
||||||
|
- https://learn.microsoft.com/en-us/previous-versions/ms809762(v=msdn.10)
|
||||||
|
- https://learn.microsoft.com/en-us/archive/msdn-magazine/2002/february/inside-windows-win32-portable-executable-file-format-in-detail
|
||||||
|
- https://learn.microsoft.com/en-us/archive/msdn-magazine/2002/march/inside-windows-an-in-depth-look-into-the-win32-portable-executable-file-format-part-2
|
||||||
|
- https://blog.kowalczyk.info/articles/pefileformat.html
|
||||||
|
- https://keyj.emphy.de/win32-pe/
|
||||||
|
- https://corkamiwiki.github.io/PE
|
||||||
|
- https://github.com/ayaka14732/TinyPE-on-Win10
|
|
@ -1,28 +1,23 @@
|
||||||
init() {
|
init() {
|
||||||
// kernel32.SetConsoleCP(cp.utf8)
|
utf8 := 65001
|
||||||
// kernel32.SetConsoleOutputCP(cp.utf8)
|
kernel32.SetConsoleCP(utf8)
|
||||||
|
kernel32.SetConsoleOutputCP(utf8)
|
||||||
main.main()
|
main.main()
|
||||||
exit()
|
exit()
|
||||||
}
|
}
|
||||||
|
|
||||||
exit() {
|
exit() {
|
||||||
// kernel32.ExitProcess(0)
|
kernel32.ExitProcess(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
crash() {
|
crash() {
|
||||||
// kernel32.ExitProcess(1)
|
kernel32.ExitProcess(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// const {
|
extern {
|
||||||
// cp {
|
kernel32 {
|
||||||
// utf8 65001
|
SetConsoleCP(cp uint)
|
||||||
// }
|
SetConsoleOutputCP(cp uint)
|
||||||
// }
|
ExitProcess(code uint)
|
||||||
|
}
|
||||||
// extern {
|
}
|
||||||
// kernel32 {
|
|
||||||
// SetConsoleCP(cp uint)
|
|
||||||
// SetConsoleOutputCP(cp uint)
|
|
||||||
// ExitProcess(code uint)
|
|
||||||
// }
|
|
||||||
// }
|
|
|
@ -1,3 +1,10 @@
|
||||||
write(_ []byte) -> (written int) {
|
write(_ []byte) -> (written int) {
|
||||||
return 0
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
extern {
|
||||||
|
kernel32 {
|
||||||
|
GetStdHandle(handle int64) -> int64
|
||||||
|
WriteConsoleA(fd int64, buffer *byte, length uint32, written *uint32) -> bool
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -5,6 +5,7 @@ import (
|
||||||
|
|
||||||
"git.urbach.dev/cli/q/src/build"
|
"git.urbach.dev/cli/q/src/build"
|
||||||
"git.urbach.dev/cli/q/src/data"
|
"git.urbach.dev/cli/q/src/data"
|
||||||
|
"git.urbach.dev/cli/q/src/dll"
|
||||||
"git.urbach.dev/cli/q/src/elf"
|
"git.urbach.dev/cli/q/src/elf"
|
||||||
"git.urbach.dev/cli/q/src/exe"
|
"git.urbach.dev/cli/q/src/exe"
|
||||||
)
|
)
|
||||||
|
@ -13,6 +14,7 @@ import (
|
||||||
type Assembler struct {
|
type Assembler struct {
|
||||||
Data data.Data
|
Data data.Data
|
||||||
Instructions []Instruction
|
Instructions []Instruction
|
||||||
|
Libraries dll.List
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append adds another instruction.
|
// Append adds another instruction.
|
||||||
|
@ -30,7 +32,7 @@ func (a *Assembler) Last() Instruction {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compile compiles the instructions to machine code.
|
// Compile compiles the instructions to machine code.
|
||||||
func (a *Assembler) Compile(b *build.Build) (code []byte, data []byte) {
|
func (a *Assembler) Compile(b *build.Build) (code []byte, data []byte, libs dll.List) {
|
||||||
data, dataLabels := a.Data.Finalize()
|
data, dataLabels := a.Data.Finalize()
|
||||||
|
|
||||||
c := compiler{
|
c := compiler{
|
||||||
|
@ -38,6 +40,7 @@ func (a *Assembler) Compile(b *build.Build) (code []byte, data []byte) {
|
||||||
data: data,
|
data: data,
|
||||||
dataLabels: dataLabels,
|
dataLabels: dataLabels,
|
||||||
labels: make(map[string]Address, 32),
|
labels: make(map[string]Address, 32),
|
||||||
|
libraries: a.Libraries,
|
||||||
}
|
}
|
||||||
|
|
||||||
switch b.Arch {
|
switch b.Arch {
|
||||||
|
@ -57,18 +60,22 @@ func (a *Assembler) Compile(b *build.Build) (code []byte, data []byte) {
|
||||||
}
|
}
|
||||||
|
|
||||||
x := exe.New(elf.HeaderEnd, b.FileAlign, b.MemoryAlign)
|
x := exe.New(elf.HeaderEnd, b.FileAlign, b.MemoryAlign)
|
||||||
x.InitSections(c.code, c.data)
|
x.InitSections(c.code, c.data, nil)
|
||||||
dataSectionOffset := x.Sections[1].MemoryOffset - x.Sections[0].MemoryOffset
|
dataSectionOffset := x.Sections[1].MemoryOffset - x.Sections[0].MemoryOffset
|
||||||
|
|
||||||
for dataLabel, address := range dataLabels {
|
for dataLabel, address := range dataLabels {
|
||||||
c.labels[dataLabel] = dataSectionOffset + address
|
c.labels[dataLabel] = dataSectionOffset + address
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if b.OS == build.Windows {
|
||||||
|
c.importsStart = x.Sections[2].MemoryOffset - x.Sections[0].MemoryOffset
|
||||||
|
}
|
||||||
|
|
||||||
for _, call := range c.deferred {
|
for _, call := range c.deferred {
|
||||||
call()
|
call()
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.code, c.data
|
return c.code, c.data, c.libraries
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge combines the contents of this assembler with another one.
|
// Merge combines the contents of this assembler with another one.
|
||||||
|
@ -81,6 +88,12 @@ func (a *Assembler) Merge(b *Assembler) {
|
||||||
|
|
||||||
a.Instructions = append(a.Instructions, b.Instructions[skip:]...)
|
a.Instructions = append(a.Instructions, b.Instructions[skip:]...)
|
||||||
maps.Copy(a.Data, b.Data)
|
maps.Copy(a.Data, b.Data)
|
||||||
|
|
||||||
|
for _, library := range b.Libraries {
|
||||||
|
for _, fn := range library.Functions {
|
||||||
|
a.Libraries = a.Libraries.Append(library.Name, fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetData sets the data for the given label.
|
// SetData sets the data for the given label.
|
||||||
|
|
|
@ -37,9 +37,9 @@ func TestAssembler(t *testing.T) {
|
||||||
final.Merge(b)
|
final.Merge(b)
|
||||||
final.Merge(c)
|
final.Merge(c)
|
||||||
|
|
||||||
code, _ := final.Compile(&build.Build{Arch: build.ARM})
|
code, _, _ := final.Compile(&build.Build{Arch: build.ARM})
|
||||||
assert.NotNil(t, code)
|
assert.NotNil(t, code)
|
||||||
|
|
||||||
code, _ = final.Compile(&build.Build{Arch: build.X86})
|
code, _, _ = final.Compile(&build.Build{Arch: build.X86})
|
||||||
assert.NotNil(t, code)
|
assert.NotNil(t, code)
|
||||||
}
|
}
|
|
@ -4,10 +4,21 @@ import "git.urbach.dev/cli/q/src/cpu"
|
||||||
|
|
||||||
type Instruction interface{}
|
type Instruction interface{}
|
||||||
|
|
||||||
|
type AndRegisterNumber struct {
|
||||||
|
Destination cpu.Register
|
||||||
|
Source cpu.Register
|
||||||
|
Number int
|
||||||
|
}
|
||||||
|
|
||||||
type Call struct {
|
type Call struct {
|
||||||
Label string
|
Label string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CallExtern struct {
|
||||||
|
Library string
|
||||||
|
Function string
|
||||||
|
}
|
||||||
|
|
||||||
type FunctionStart struct{}
|
type FunctionStart struct{}
|
||||||
type FunctionEnd struct{}
|
type FunctionEnd struct{}
|
||||||
|
|
||||||
|
@ -35,4 +46,11 @@ type MoveRegisterRegister struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Return struct{}
|
type Return struct{}
|
||||||
|
|
||||||
|
type SubRegisterNumber struct {
|
||||||
|
Destination cpu.Register
|
||||||
|
Source cpu.Register
|
||||||
|
Number int
|
||||||
|
}
|
||||||
|
|
||||||
type Syscall struct{}
|
type Syscall struct{}
|
|
@ -1,11 +1,15 @@
|
||||||
package asm
|
package asm
|
||||||
|
|
||||||
|
import "git.urbach.dev/cli/q/src/dll"
|
||||||
|
|
||||||
type compiler struct {
|
type compiler struct {
|
||||||
code []byte
|
code []byte
|
||||||
data []byte
|
data []byte
|
||||||
dataLabels map[string]Address
|
dataLabels map[string]Address
|
||||||
labels map[string]Address
|
labels map[string]Address
|
||||||
deferred []func()
|
libraries dll.List
|
||||||
|
importsStart int
|
||||||
|
deferred []func()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *compiler) Defer(call func()) {
|
func (c *compiler) Defer(call func()) {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package asm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"git.urbach.dev/cli/q/src/sizeof"
|
"git.urbach.dev/cli/q/src/sizeof"
|
||||||
"git.urbach.dev/cli/q/src/x86"
|
"git.urbach.dev/cli/q/src/x86"
|
||||||
|
@ -13,6 +14,18 @@ type compilerX86 struct {
|
||||||
|
|
||||||
func (c *compilerX86) Compile(instr Instruction) {
|
func (c *compilerX86) Compile(instr Instruction) {
|
||||||
switch instr := instr.(type) {
|
switch instr := instr.(type) {
|
||||||
|
case *AndRegisterNumber:
|
||||||
|
if instr.Destination != instr.Source {
|
||||||
|
c.code = x86.MoveRegisterRegister(c.code, instr.Destination, instr.Source)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.code = x86.AndRegisterNumber(c.code, instr.Destination, instr.Number)
|
||||||
|
case *SubRegisterNumber:
|
||||||
|
if instr.Destination != instr.Source {
|
||||||
|
c.code = x86.MoveRegisterRegister(c.code, instr.Destination, instr.Source)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.code = x86.SubRegisterNumber(c.code, instr.Destination, instr.Number)
|
||||||
case *Call:
|
case *Call:
|
||||||
c.code = x86.Call(c.code, 0)
|
c.code = x86.Call(c.code, 0)
|
||||||
end := len(c.code)
|
end := len(c.code)
|
||||||
|
@ -24,6 +37,21 @@ func (c *compilerX86) Compile(instr Instruction) {
|
||||||
panic("unknown label: " + instr.Label)
|
panic("unknown label: " + instr.Label)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
offset := address - end
|
||||||
|
binary.LittleEndian.PutUint32(c.code[end-4:end], uint32(offset))
|
||||||
|
})
|
||||||
|
case *CallExtern:
|
||||||
|
c.code = x86.CallAt(c.code, 0)
|
||||||
|
end := len(c.code)
|
||||||
|
|
||||||
|
c.Defer(func() {
|
||||||
|
index := c.libraries.Index(instr.Library, instr.Function)
|
||||||
|
|
||||||
|
if index == -1 {
|
||||||
|
panic(fmt.Sprintf("unknown extern function '%s' in library '%s'", instr.Function, instr.Library))
|
||||||
|
}
|
||||||
|
|
||||||
|
address := c.importsStart + index*8
|
||||||
offset := address - end
|
offset := address - end
|
||||||
binary.LittleEndian.PutUint32(c.code[end-4:end], uint32(offset))
|
binary.LittleEndian.PutUint32(c.code[end-4:end], uint32(offset))
|
||||||
})
|
})
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"git.urbach.dev/cli/q/src/cpu"
|
"git.urbach.dev/cli/q/src/cpu"
|
||||||
"git.urbach.dev/cli/q/src/ssa"
|
"git.urbach.dev/cli/q/src/ssa"
|
||||||
"git.urbach.dev/cli/q/src/types"
|
"git.urbach.dev/cli/q/src/types"
|
||||||
|
"git.urbach.dev/cli/q/src/x86"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Compile turns a function into machine code.
|
// Compile turns a function into machine code.
|
||||||
|
@ -72,11 +73,19 @@ func (f *Function) Compile() {
|
||||||
for instr := range f.Values {
|
for instr := range f.Values {
|
||||||
switch instr := instr.(type) {
|
switch instr := instr.(type) {
|
||||||
case *ssa.Call:
|
case *ssa.Call:
|
||||||
f.mv(instr.Arguments[1:], f.CPU.Call)
|
arg := instr.Arguments[0].(*ssa.Function)
|
||||||
|
fn := f.All.Functions[arg.UniqueName]
|
||||||
|
|
||||||
switch arg := instr.Arguments[0].(type) {
|
if fn.IsExtern() {
|
||||||
case *ssa.Function:
|
f.mv(instr.Arguments[1:], f.CPU.ExternCall)
|
||||||
f.Assembler.Instructions = append(f.Assembler.Instructions, &asm.Call{Label: arg.UniqueName})
|
f.Assembler.Append(&asm.MoveRegisterRegister{Destination: x86.R5, Source: x86.SP})
|
||||||
|
f.Assembler.Append(&asm.AndRegisterNumber{Destination: x86.SP, Source: x86.SP, Number: -16})
|
||||||
|
f.Assembler.Append(&asm.SubRegisterNumber{Destination: x86.SP, Source: x86.SP, Number: 32})
|
||||||
|
f.Assembler.Append(&asm.CallExtern{Library: fn.Package, Function: fn.Name})
|
||||||
|
f.Assembler.Append(&asm.MoveRegisterRegister{Destination: x86.SP, Source: x86.R5})
|
||||||
|
} else {
|
||||||
|
f.mv(instr.Arguments[1:], f.CPU.Call)
|
||||||
|
f.Assembler.Append(&asm.Call{Label: fn.UniqueName})
|
||||||
}
|
}
|
||||||
|
|
||||||
case *ssa.Syscall:
|
case *ssa.Syscall:
|
||||||
|
|
|
@ -137,7 +137,12 @@ func (f *Function) Evaluate(expr *expression.Expression) (ssa.Value, error) {
|
||||||
return nil, errors.New(&UnknownIdentifier{Name: label}, f.File, left.Token.Position)
|
return nil, errors.New(&UnknownIdentifier{Name: label}, f.File, left.Token.Position)
|
||||||
}
|
}
|
||||||
|
|
||||||
f.Dependencies.Add(function)
|
if function.IsExtern() {
|
||||||
|
f.Assembler.Libraries = f.Assembler.Libraries.Append(function.Package, function.Name)
|
||||||
|
} else {
|
||||||
|
f.Dependencies.Add(function)
|
||||||
|
}
|
||||||
|
|
||||||
v := f.AppendFunction(function.UniqueName, function.Type)
|
v := f.AppendFunction(function.UniqueName, function.Type)
|
||||||
v.Source = ssa.Source(expr.Source)
|
v.Source = ssa.Source(expr.Source)
|
||||||
return v, nil
|
return v, nil
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
type Function struct {
|
type Function struct {
|
||||||
ssa.IR
|
ssa.IR
|
||||||
Name string
|
Name string
|
||||||
|
Package string
|
||||||
UniqueName string
|
UniqueName string
|
||||||
File *fs.File
|
File *fs.File
|
||||||
Input []*ssa.Parameter
|
Input []*ssa.Parameter
|
||||||
|
@ -31,11 +32,12 @@ type Function struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFunction creates a new function.
|
// NewFunction creates a new function.
|
||||||
func NewFunction(name string, file *fs.File) *Function {
|
func NewFunction(name string, pkg string, file *fs.File) *Function {
|
||||||
return &Function{
|
return &Function{
|
||||||
Name: name,
|
Name: name,
|
||||||
|
Package: pkg,
|
||||||
|
UniqueName: fmt.Sprintf("%s.%s", pkg, name),
|
||||||
File: file,
|
File: file,
|
||||||
UniqueName: fmt.Sprintf("%s.%s", file.Package, name),
|
|
||||||
Identifiers: make(map[string]ssa.Value, 8),
|
Identifiers: make(map[string]ssa.Value, 8),
|
||||||
IR: ssa.IR{
|
IR: ssa.IR{
|
||||||
Blocks: []*ssa.Block{
|
Blocks: []*ssa.Block{
|
||||||
|
|
|
@ -2,6 +2,7 @@ package cpu
|
||||||
|
|
||||||
// CPU represents the processor.
|
// CPU represents the processor.
|
||||||
type CPU struct {
|
type CPU struct {
|
||||||
Call []Register
|
Call []Register
|
||||||
Syscall []Register
|
Syscall []Register
|
||||||
|
ExternCall []Register
|
||||||
}
|
}
|
6
src/dll/Library.go
Normal file
6
src/dll/Library.go
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package dll
|
||||||
|
|
||||||
|
type Library struct {
|
||||||
|
Name string
|
||||||
|
Functions []string
|
||||||
|
}
|
55
src/dll/List.go
Normal file
55
src/dll/List.go
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
package dll
|
||||||
|
|
||||||
|
import "slices"
|
||||||
|
|
||||||
|
// List is a slice of DLLs.
|
||||||
|
type List []Library
|
||||||
|
|
||||||
|
// Append adds a function for the given DLL if it doesn't exist yet.
|
||||||
|
func (list List) Append(dllName string, funcName string) List {
|
||||||
|
for i, dll := range list {
|
||||||
|
if dll.Name != dllName {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if slices.Contains(dll.Functions, funcName) {
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
list[i].Functions = append(list[i].Functions, funcName)
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
return append(list, Library{Name: dllName, Functions: []string{funcName}})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains returns true if the library exists.
|
||||||
|
func (list List) Contains(dllName string) bool {
|
||||||
|
for _, dll := range list {
|
||||||
|
if dll.Name == dllName {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index returns the position of the given function name.
|
||||||
|
func (list List) Index(dllName string, funcName string) int {
|
||||||
|
index := 0
|
||||||
|
|
||||||
|
for _, dll := range list {
|
||||||
|
if dll.Name != dllName {
|
||||||
|
index += len(dll.Functions) + 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, fn := range dll.Functions {
|
||||||
|
if fn == funcName {
|
||||||
|
return index + i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1
|
||||||
|
}
|
34
src/dll/List_test.go
Normal file
34
src/dll/List_test.go
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package dll_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.urbach.dev/cli/q/src/dll"
|
||||||
|
"git.urbach.dev/go/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestList(t *testing.T) {
|
||||||
|
libs := dll.List{}
|
||||||
|
assert.False(t, libs.Contains("kernel32"))
|
||||||
|
assert.Equal(t, -1, libs.Index("kernel32", "ExitProcess"))
|
||||||
|
|
||||||
|
libs = libs.Append("kernel32", "ExitProcess")
|
||||||
|
assert.True(t, libs.Contains("kernel32"))
|
||||||
|
assert.Equal(t, 0, libs.Index("kernel32", "ExitProcess"))
|
||||||
|
|
||||||
|
libs = libs.Append("user32", "MessageBox")
|
||||||
|
assert.True(t, libs.Contains("kernel32"))
|
||||||
|
assert.True(t, libs.Contains("user32"))
|
||||||
|
assert.Equal(t, 0, libs.Index("kernel32", "ExitProcess"))
|
||||||
|
assert.Equal(t, 2, libs.Index("user32", "MessageBox"))
|
||||||
|
|
||||||
|
libs = libs.Append("kernel32", "SetConsoleCP")
|
||||||
|
assert.Equal(t, 0, libs.Index("kernel32", "ExitProcess"))
|
||||||
|
assert.Equal(t, 1, libs.Index("kernel32", "SetConsoleCP"))
|
||||||
|
assert.Equal(t, 3, libs.Index("user32", "MessageBox"))
|
||||||
|
|
||||||
|
libs = libs.Append("kernel32", "ExitProcess")
|
||||||
|
assert.Equal(t, 0, libs.Index("kernel32", "ExitProcess"))
|
||||||
|
assert.Equal(t, 1, libs.Index("kernel32", "SetConsoleCP"))
|
||||||
|
assert.Equal(t, 3, libs.Index("user32", "MessageBox"))
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"git.urbach.dev/cli/q/src/data"
|
"git.urbach.dev/cli/q/src/data"
|
||||||
"git.urbach.dev/cli/q/src/elf"
|
"git.urbach.dev/cli/q/src/elf"
|
||||||
"git.urbach.dev/cli/q/src/macho"
|
"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.
|
||||||
|
@ -33,13 +34,15 @@ func WriteFile(executable string, b *build.Build, env *core.Environment) error {
|
||||||
final.Merge(&f.Assembler)
|
final.Merge(&f.Assembler)
|
||||||
})
|
})
|
||||||
|
|
||||||
code, data := final.Compile(b)
|
code, data, libs := final.Compile(b)
|
||||||
|
|
||||||
switch b.OS {
|
switch b.OS {
|
||||||
case build.Linux:
|
case build.Linux:
|
||||||
elf.Write(file, b, code, data)
|
elf.Write(file, b, code, data)
|
||||||
case build.Mac:
|
case build.Mac:
|
||||||
macho.Write(file, b, code, data)
|
macho.Write(file, b, code, data)
|
||||||
|
case build.Windows:
|
||||||
|
pe.Write(file, b, code, data, libs)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = file.Close()
|
err = file.Close()
|
||||||
|
|
23
src/pe/Arch.go
Normal file
23
src/pe/Arch.go
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
package pe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.urbach.dev/cli/q/src/build"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
IMAGE_FILE_MACHINE_AMD64 = 0x8664
|
||||||
|
IMAGE_FILE_MACHINE_ARM64 = 0xAA64
|
||||||
|
IMAGE_FILE_MACHINE_RISCV64 = 0x5064
|
||||||
|
)
|
||||||
|
|
||||||
|
// Arch returns the CPU architecture used in the PE header.
|
||||||
|
func Arch(arch build.Arch) uint16 {
|
||||||
|
switch arch {
|
||||||
|
case build.ARM:
|
||||||
|
return IMAGE_FILE_MACHINE_ARM64
|
||||||
|
case build.X86:
|
||||||
|
return IMAGE_FILE_MACHINE_AMD64
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
21
src/pe/Characteristics.go
Normal file
21
src/pe/Characteristics.go
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package pe
|
||||||
|
|
||||||
|
type Characteristics uint16
|
||||||
|
|
||||||
|
const (
|
||||||
|
IMAGE_FILE_RELOCS_STRIPPED Characteristics = 0x0001
|
||||||
|
IMAGE_FILE_EXECUTABLE_IMAGE Characteristics = 0x0002
|
||||||
|
IMAGE_FILE_LINE_NUMS_STRIPPED Characteristics = 0x0004
|
||||||
|
IMAGE_FILE_LOCAL_SYMS_STRIPPED Characteristics = 0x0008
|
||||||
|
IMAGE_FILE_AGGRESSIVE_WS_TRIM Characteristics = 0x0010
|
||||||
|
IMAGE_FILE_LARGE_ADDRESS_AWARE Characteristics = 0x0020
|
||||||
|
IMAGE_FILE_BYTES_REVERSED_LO Characteristics = 0x0080
|
||||||
|
IMAGE_FILE_32BIT_MACHINE Characteristics = 0x0100
|
||||||
|
IMAGE_FILE_DEBUG_STRIPPED Characteristics = 0x0200
|
||||||
|
IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP Characteristics = 0x0400
|
||||||
|
IMAGE_FILE_NET_RUN_FROM_SWAP Characteristics = 0x0800
|
||||||
|
IMAGE_FILE_SYSTEM Characteristics = 0x1000
|
||||||
|
IMAGE_FILE_DLL Characteristics = 0x2000
|
||||||
|
IMAGE_FILE_UP_SYSTEM_ONLY Characteristics = 0x4000
|
||||||
|
IMAGE_FILE_BYTES_REVERSED_HI Characteristics = 0x8000
|
||||||
|
)
|
7
src/pe/Constants.go
Normal file
7
src/pe/Constants.go
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
package pe
|
||||||
|
|
||||||
|
const (
|
||||||
|
BaseAddress = 0x400000
|
||||||
|
NumSections = 3
|
||||||
|
HeaderEnd = DOSHeaderSize + NTHeaderSize + OptionalHeader64Size + SectionHeaderSize*NumSections
|
||||||
|
)
|
11
src/pe/DLLImport.go
Normal file
11
src/pe/DLLImport.go
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
package pe
|
||||||
|
|
||||||
|
const DLLImportSize = 20
|
||||||
|
|
||||||
|
type DLLImport struct {
|
||||||
|
RvaFunctionNameList uint32
|
||||||
|
TimeDateStamp uint32
|
||||||
|
ForwarderChain uint32
|
||||||
|
RvaModuleName uint32
|
||||||
|
RvaFunctionAddressList uint32
|
||||||
|
}
|
11
src/pe/DOSHeader.go
Normal file
11
src/pe/DOSHeader.go
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
package pe
|
||||||
|
|
||||||
|
const DOSHeaderSize = 64
|
||||||
|
|
||||||
|
// DOSHeader is at the beginning of each EXE file and nowadays just points
|
||||||
|
// to the PEHeader using an absolute file offset.
|
||||||
|
type DOSHeader struct {
|
||||||
|
Magic [4]byte
|
||||||
|
_ [56]byte
|
||||||
|
NTHeaderOffset uint32
|
||||||
|
}
|
6
src/pe/DataDirectory.go
Normal file
6
src/pe/DataDirectory.go
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package pe
|
||||||
|
|
||||||
|
type DataDirectory struct {
|
||||||
|
VirtualAddress uint32
|
||||||
|
Size uint32
|
||||||
|
}
|
17
src/pe/DllCharacteristics.go
Normal file
17
src/pe/DllCharacteristics.go
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
package pe
|
||||||
|
|
||||||
|
type DllCharacteristics uint16
|
||||||
|
|
||||||
|
const (
|
||||||
|
IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA DllCharacteristics = 0x0020
|
||||||
|
IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE DllCharacteristics = 0x0040
|
||||||
|
IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY DllCharacteristics = 0x0080
|
||||||
|
IMAGE_DLLCHARACTERISTICS_NX_COMPAT DllCharacteristics = 0x0100
|
||||||
|
IMAGE_DLLCHARACTERISTICS_NO_ISOLATION DllCharacteristics = 0x0200
|
||||||
|
IMAGE_DLLCHARACTERISTICS_NO_SEH DllCharacteristics = 0x0400
|
||||||
|
IMAGE_DLLCHARACTERISTICS_NO_BIND DllCharacteristics = 0x0800
|
||||||
|
IMAGE_DLLCHARACTERISTICS_APPCONTAINER DllCharacteristics = 0x1000
|
||||||
|
IMAGE_DLLCHARACTERISTICS_WDM_DRIVER DllCharacteristics = 0x2000
|
||||||
|
IMAGE_DLLCHARACTERISTICS_GUARD_CF DllCharacteristics = 0x4000
|
||||||
|
IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE DllCharacteristics = 0x8000
|
||||||
|
)
|
147
src/pe/EXE.go
Normal file
147
src/pe/EXE.go
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
package pe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"git.urbach.dev/cli/q/src/build"
|
||||||
|
"git.urbach.dev/cli/q/src/dll"
|
||||||
|
"git.urbach.dev/cli/q/src/exe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EXE is the portable executable format used on Windows.
|
||||||
|
type EXE struct {
|
||||||
|
DOSHeader
|
||||||
|
NTHeader
|
||||||
|
OptionalHeader64
|
||||||
|
Sections []SectionHeader
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes the EXE file to the given writer.
|
||||||
|
func Write(writer io.WriteSeeker, b *build.Build, codeBytes []byte, dataBytes []byte, libs dll.List) {
|
||||||
|
x := exe.New(HeaderEnd, b.FileAlign, b.MemoryAlign)
|
||||||
|
x.InitSections(codeBytes, dataBytes, nil)
|
||||||
|
code := x.Sections[0]
|
||||||
|
data := x.Sections[1]
|
||||||
|
imports := x.Sections[2]
|
||||||
|
subSystem := IMAGE_SUBSYSTEM_WINDOWS_CUI
|
||||||
|
arch := Arch(b.Arch)
|
||||||
|
|
||||||
|
importsData, dllData, dllImports, dllDataStart := importLibraries(libs, imports.FileOffset)
|
||||||
|
buffer := bytes.Buffer{}
|
||||||
|
binary.Write(&buffer, binary.LittleEndian, &importsData)
|
||||||
|
binary.Write(&buffer, binary.LittleEndian, &dllData)
|
||||||
|
binary.Write(&buffer, binary.LittleEndian, &dllImports)
|
||||||
|
imports.Bytes = buffer.Bytes()
|
||||||
|
importDirectoryStart := dllDataStart + len(dllData)
|
||||||
|
importDirectorySize := DLLImportSize * len(dllImports)
|
||||||
|
imageSize := exe.Align(imports.MemoryOffset+len(imports.Bytes), b.MemoryAlign)
|
||||||
|
|
||||||
|
if libs.Contains("user32") {
|
||||||
|
subSystem = IMAGE_SUBSYSTEM_WINDOWS_GUI
|
||||||
|
}
|
||||||
|
|
||||||
|
pe := &EXE{
|
||||||
|
DOSHeader: DOSHeader{
|
||||||
|
Magic: [4]byte{'M', 'Z', 0, 0},
|
||||||
|
NTHeaderOffset: DOSHeaderSize,
|
||||||
|
},
|
||||||
|
NTHeader: NTHeader{
|
||||||
|
Signature: [4]byte{'P', 'E', 0, 0},
|
||||||
|
Machine: arch,
|
||||||
|
NumberOfSections: uint16(NumSections),
|
||||||
|
TimeDateStamp: 0,
|
||||||
|
PointerToSymbolTable: 0,
|
||||||
|
NumberOfSymbols: 0,
|
||||||
|
SizeOfOptionalHeader: OptionalHeader64Size,
|
||||||
|
Characteristics: IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_LARGE_ADDRESS_AWARE,
|
||||||
|
},
|
||||||
|
OptionalHeader64: OptionalHeader64{
|
||||||
|
Magic: 0x020B, // PE32+ / 64-bit executable
|
||||||
|
MajorLinkerVersion: 0x0E,
|
||||||
|
MinorLinkerVersion: 0x16,
|
||||||
|
SizeOfCode: uint32(len(code.Bytes)),
|
||||||
|
SizeOfInitializedData: 0,
|
||||||
|
SizeOfUninitializedData: 0,
|
||||||
|
AddressOfEntryPoint: uint32(code.MemoryOffset),
|
||||||
|
BaseOfCode: uint32(code.MemoryOffset),
|
||||||
|
ImageBase: BaseAddress,
|
||||||
|
SectionAlignment: uint32(b.MemoryAlign), // power of 2, must be greater than or equal to FileAlignment
|
||||||
|
FileAlignment: uint32(b.FileAlign), // power of 2
|
||||||
|
MajorOperatingSystemVersion: 0x06,
|
||||||
|
MinorOperatingSystemVersion: 0,
|
||||||
|
MajorImageVersion: 0,
|
||||||
|
MinorImageVersion: 0,
|
||||||
|
MajorSubsystemVersion: 0x06,
|
||||||
|
MinorSubsystemVersion: 0,
|
||||||
|
Win32VersionValue: 0,
|
||||||
|
SizeOfImage: uint32(imageSize), // a multiple of SectionAlignment
|
||||||
|
SizeOfHeaders: uint32(code.FileOffset), // section bodies begin here
|
||||||
|
CheckSum: 0,
|
||||||
|
Subsystem: subSystem,
|
||||||
|
DllCharacteristics: IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA | IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE | IMAGE_DLLCHARACTERISTICS_NX_COMPAT | IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE,
|
||||||
|
SizeOfStackReserve: 0x100000,
|
||||||
|
SizeOfStackCommit: 0x1000,
|
||||||
|
SizeOfHeapReserve: 0x100000,
|
||||||
|
SizeOfHeapCommit: 0x1000,
|
||||||
|
LoaderFlags: 0,
|
||||||
|
NumberOfRvaAndSizes: 16,
|
||||||
|
DataDirectory: [16]DataDirectory{
|
||||||
|
{VirtualAddress: 0, Size: 0},
|
||||||
|
{VirtualAddress: uint32(importDirectoryStart), Size: uint32(importDirectorySize)}, // RVA of the imported function table
|
||||||
|
{VirtualAddress: 0, Size: 0},
|
||||||
|
{VirtualAddress: 0, Size: 0},
|
||||||
|
{VirtualAddress: 0, Size: 0},
|
||||||
|
{VirtualAddress: 0, Size: 0},
|
||||||
|
{VirtualAddress: 0, Size: 0},
|
||||||
|
{VirtualAddress: 0, Size: 0},
|
||||||
|
{VirtualAddress: 0, Size: 0},
|
||||||
|
{VirtualAddress: 0, Size: 0},
|
||||||
|
{VirtualAddress: 0, Size: 0},
|
||||||
|
{VirtualAddress: 0, Size: 0},
|
||||||
|
{VirtualAddress: uint32(imports.MemoryOffset), Size: uint32(len(importsData) * 8)}, // RVA of the import address table
|
||||||
|
{VirtualAddress: 0, Size: 0},
|
||||||
|
{VirtualAddress: 0, Size: 0},
|
||||||
|
{VirtualAddress: 0, Size: 0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Sections: []SectionHeader{
|
||||||
|
{
|
||||||
|
Name: [8]byte{'.', 't', 'e', 'x', 't'},
|
||||||
|
VirtualSize: uint32(len(code.Bytes)),
|
||||||
|
VirtualAddress: uint32(code.MemoryOffset),
|
||||||
|
RawSize: uint32(len(code.Bytes)), // must be a multiple of FileAlignment
|
||||||
|
RawAddress: uint32(code.FileOffset), // must be a multiple of FileAlignment
|
||||||
|
Characteristics: IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: [8]byte{'.', 'r', 'd', 'a', 't', 'a'},
|
||||||
|
VirtualSize: uint32(len(data.Bytes)),
|
||||||
|
VirtualAddress: uint32(data.MemoryOffset),
|
||||||
|
RawSize: uint32(len(data.Bytes)),
|
||||||
|
RawAddress: uint32(data.FileOffset),
|
||||||
|
Characteristics: IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: [8]byte{'.', 'i', 'd', 'a', 't', 'a'},
|
||||||
|
VirtualSize: uint32(len(imports.Bytes)),
|
||||||
|
VirtualAddress: uint32(imports.MemoryOffset),
|
||||||
|
RawSize: uint32(len(imports.Bytes)),
|
||||||
|
RawAddress: uint32(imports.FileOffset),
|
||||||
|
Characteristics: IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
binary.Write(writer, binary.LittleEndian, &pe.DOSHeader)
|
||||||
|
binary.Write(writer, binary.LittleEndian, &pe.NTHeader)
|
||||||
|
binary.Write(writer, binary.LittleEndian, &pe.OptionalHeader64)
|
||||||
|
binary.Write(writer, binary.LittleEndian, &pe.Sections)
|
||||||
|
writer.Seek(int64(code.Padding), io.SeekCurrent)
|
||||||
|
writer.Write(code.Bytes)
|
||||||
|
writer.Seek(int64(data.Padding), io.SeekCurrent)
|
||||||
|
writer.Write(data.Bytes)
|
||||||
|
writer.Seek(int64(imports.Padding), io.SeekCurrent)
|
||||||
|
writer.Write(imports.Bytes)
|
||||||
|
}
|
15
src/pe/EXE_test.go
Normal file
15
src/pe/EXE_test.go
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package pe_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.urbach.dev/cli/q/src/build"
|
||||||
|
"git.urbach.dev/cli/q/src/exe"
|
||||||
|
"git.urbach.dev/cli/q/src/pe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWrite(t *testing.T) {
|
||||||
|
pe.Write(&exe.Discard{}, &build.Build{Arch: build.ARM, FileAlign: 0x4000, MemoryAlign: 0x4000}, nil, nil, nil)
|
||||||
|
pe.Write(&exe.Discard{}, &build.Build{Arch: build.X86, FileAlign: 0x1000, MemoryAlign: 0x1000}, nil, nil, nil)
|
||||||
|
pe.Write(&exe.Discard{}, &build.Build{Arch: build.UnknownArch, FileAlign: 0x1000, MemoryAlign: 0x1000}, nil, nil, nil)
|
||||||
|
}
|
14
src/pe/NTHeader.go
Normal file
14
src/pe/NTHeader.go
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
package pe
|
||||||
|
|
||||||
|
const NTHeaderSize = 24
|
||||||
|
|
||||||
|
type NTHeader struct {
|
||||||
|
Signature [4]byte
|
||||||
|
Machine uint16
|
||||||
|
NumberOfSections uint16
|
||||||
|
TimeDateStamp uint32
|
||||||
|
PointerToSymbolTable uint32
|
||||||
|
NumberOfSymbols uint32
|
||||||
|
SizeOfOptionalHeader uint16
|
||||||
|
Characteristics Characteristics
|
||||||
|
}
|
36
src/pe/OptionalHeader64.go
Normal file
36
src/pe/OptionalHeader64.go
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package pe
|
||||||
|
|
||||||
|
const OptionalHeader64Size = 240
|
||||||
|
|
||||||
|
type OptionalHeader64 struct {
|
||||||
|
Magic uint16
|
||||||
|
MajorLinkerVersion uint8
|
||||||
|
MinorLinkerVersion uint8
|
||||||
|
SizeOfCode uint32
|
||||||
|
SizeOfInitializedData uint32
|
||||||
|
SizeOfUninitializedData uint32
|
||||||
|
AddressOfEntryPoint uint32
|
||||||
|
BaseOfCode uint32
|
||||||
|
ImageBase uint64
|
||||||
|
SectionAlignment uint32
|
||||||
|
FileAlignment uint32
|
||||||
|
MajorOperatingSystemVersion uint16
|
||||||
|
MinorOperatingSystemVersion uint16
|
||||||
|
MajorImageVersion uint16
|
||||||
|
MinorImageVersion uint16
|
||||||
|
MajorSubsystemVersion uint16
|
||||||
|
MinorSubsystemVersion uint16
|
||||||
|
Win32VersionValue uint32
|
||||||
|
SizeOfImage uint32
|
||||||
|
SizeOfHeaders uint32
|
||||||
|
CheckSum uint32
|
||||||
|
Subsystem Subsystem
|
||||||
|
DllCharacteristics DllCharacteristics
|
||||||
|
SizeOfStackReserve uint64
|
||||||
|
SizeOfStackCommit uint64
|
||||||
|
SizeOfHeapReserve uint64
|
||||||
|
SizeOfHeapCommit uint64
|
||||||
|
LoaderFlags uint32
|
||||||
|
NumberOfRvaAndSizes uint32
|
||||||
|
DataDirectory [16]DataDirectory
|
||||||
|
}
|
14
src/pe/SectionCharacteristics.go
Normal file
14
src/pe/SectionCharacteristics.go
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
package pe
|
||||||
|
|
||||||
|
type SectionCharacteristics uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
IMAGE_SCN_CNT_CODE SectionCharacteristics = 0x00000020
|
||||||
|
IMAGE_SCN_CNT_INITIALIZED_DATA SectionCharacteristics = 0x00000040
|
||||||
|
IMAGE_SCN_CNT_UNINITIALIZED_DATA SectionCharacteristics = 0x00000080
|
||||||
|
IMAGE_SCN_LNK_COMDAT SectionCharacteristics = 0x00001000
|
||||||
|
IMAGE_SCN_MEM_DISCARDABLE SectionCharacteristics = 0x02000000
|
||||||
|
IMAGE_SCN_MEM_EXECUTE SectionCharacteristics = 0x20000000
|
||||||
|
IMAGE_SCN_MEM_READ SectionCharacteristics = 0x40000000
|
||||||
|
IMAGE_SCN_MEM_WRITE SectionCharacteristics = 0x80000000
|
||||||
|
)
|
16
src/pe/SectionHeader.go
Normal file
16
src/pe/SectionHeader.go
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package pe
|
||||||
|
|
||||||
|
const SectionHeaderSize = 40
|
||||||
|
|
||||||
|
type SectionHeader struct {
|
||||||
|
Name [8]byte
|
||||||
|
VirtualSize uint32
|
||||||
|
VirtualAddress uint32
|
||||||
|
RawSize uint32
|
||||||
|
RawAddress uint32
|
||||||
|
PointerToRelocations uint32
|
||||||
|
PointerToLineNumbers uint32
|
||||||
|
NumberOfRelocations uint16
|
||||||
|
NumberOfLineNumbers uint16
|
||||||
|
Characteristics SectionCharacteristics
|
||||||
|
}
|
20
src/pe/Subsystem.go
Normal file
20
src/pe/Subsystem.go
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
package pe
|
||||||
|
|
||||||
|
type Subsystem uint16
|
||||||
|
|
||||||
|
const (
|
||||||
|
IMAGE_SUBSYSTEM_UNKNOWN Subsystem = 0
|
||||||
|
IMAGE_SUBSYSTEM_NATIVE Subsystem = 1
|
||||||
|
IMAGE_SUBSYSTEM_WINDOWS_GUI Subsystem = 2
|
||||||
|
IMAGE_SUBSYSTEM_WINDOWS_CUI Subsystem = 3
|
||||||
|
IMAGE_SUBSYSTEM_OS2_CUI Subsystem = 5
|
||||||
|
IMAGE_SUBSYSTEM_POSIX_CUI Subsystem = 7
|
||||||
|
IMAGE_SUBSYSTEM_NATIVE_WINDOWS Subsystem = 8
|
||||||
|
IMAGE_SUBSYSTEM_WINDOWS_CE_GUI Subsystem = 9
|
||||||
|
IMAGE_SUBSYSTEM_EFI_APPLICATION Subsystem = 10
|
||||||
|
IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER Subsystem = 11
|
||||||
|
IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER Subsystem = 12
|
||||||
|
IMAGE_SUBSYSTEM_EFI_ROM Subsystem = 13
|
||||||
|
IMAGE_SUBSYSTEM_XBOX Subsystem = 14
|
||||||
|
IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION Subsystem = 16
|
||||||
|
)
|
60
src/pe/importLibraries.go
Normal file
60
src/pe/importLibraries.go
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
package pe
|
||||||
|
|
||||||
|
import "git.urbach.dev/cli/q/src/dll"
|
||||||
|
|
||||||
|
// importLibraries generates the import address table which contains the addresses of functions imported from DLLs.
|
||||||
|
func importLibraries(dlls dll.List, importsStart int) ([]uint64, []byte, []DLLImport, int) {
|
||||||
|
imports := make([]uint64, 0)
|
||||||
|
dllData := make([]byte, 0)
|
||||||
|
dllImports := []DLLImport{}
|
||||||
|
|
||||||
|
for _, library := range dlls {
|
||||||
|
functionsStart := len(imports) * 8
|
||||||
|
dllNamePos := len(dllData)
|
||||||
|
dllData = append(dllData, library.Name...)
|
||||||
|
dllData = append(dllData, ".dll"...)
|
||||||
|
dllData = append(dllData, 0x00)
|
||||||
|
|
||||||
|
dllImports = append(dllImports, DLLImport{
|
||||||
|
RvaFunctionNameList: uint32(importsStart + functionsStart),
|
||||||
|
TimeDateStamp: 0,
|
||||||
|
ForwarderChain: 0,
|
||||||
|
RvaModuleName: uint32(dllNamePos),
|
||||||
|
RvaFunctionAddressList: uint32(importsStart + functionsStart),
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, fn := range library.Functions {
|
||||||
|
if len(dllData)&1 != 0 {
|
||||||
|
dllData = append(dllData, 0x00) // align the next entry on an even boundary
|
||||||
|
}
|
||||||
|
|
||||||
|
offset := len(dllData)
|
||||||
|
dllData = append(dllData, 0x00, 0x00)
|
||||||
|
dllData = append(dllData, fn...)
|
||||||
|
dllData = append(dllData, 0x00)
|
||||||
|
|
||||||
|
imports = append(imports, uint64(offset))
|
||||||
|
}
|
||||||
|
|
||||||
|
imports = append(imports, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
dllDataStart := importsStart + len(imports)*8
|
||||||
|
|
||||||
|
for i := range imports {
|
||||||
|
if imports[i] == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
imports[i] += uint64(dllDataStart)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range dllImports {
|
||||||
|
dllImports[i].RvaModuleName += uint32(dllDataStart)
|
||||||
|
}
|
||||||
|
|
||||||
|
// a zeroed structure marks the end of the list
|
||||||
|
dllImports = append(dllImports, DLLImport{})
|
||||||
|
|
||||||
|
return imports, dllData, dllImports, dllDataStart
|
||||||
|
}
|
68
src/scanner/scanExtern.go
Normal file
68
src/scanner/scanExtern.go
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
package scanner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.urbach.dev/cli/q/src/errors"
|
||||||
|
"git.urbach.dev/cli/q/src/fs"
|
||||||
|
"git.urbach.dev/cli/q/src/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
// scanExtern scans a block of external libraries.
|
||||||
|
func (s *scanner) scanExtern(file *fs.File, tokens token.List, i int) (int, error) {
|
||||||
|
i++
|
||||||
|
|
||||||
|
if tokens[i].Kind != token.BlockStart {
|
||||||
|
return i, errors.New(MissingBlockStart, file, tokens[i].Position)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i < len(tokens) {
|
||||||
|
switch tokens[i].Kind {
|
||||||
|
case token.Identifier:
|
||||||
|
var err error
|
||||||
|
i, err = s.scanExternLibrary(file, tokens, i)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
case token.BlockEnd:
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
return i, errors.New(MissingBlockEnd, file, tokens[i].Position)
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanExternLibrary scans a block of external function declarations.
|
||||||
|
func (s *scanner) scanExternLibrary(file *fs.File, tokens token.List, i int) (int, error) {
|
||||||
|
dllName := tokens[i].String(file.Bytes)
|
||||||
|
i++
|
||||||
|
|
||||||
|
if tokens[i].Kind != token.BlockStart {
|
||||||
|
return i, errors.New(MissingBlockStart, file, tokens[i].Position)
|
||||||
|
}
|
||||||
|
|
||||||
|
i++
|
||||||
|
|
||||||
|
for i < len(tokens) {
|
||||||
|
switch tokens[i].Kind {
|
||||||
|
case token.Identifier:
|
||||||
|
function, j, err := scanSignature(file, dllName, tokens, i, token.NewLine)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return j, err
|
||||||
|
}
|
||||||
|
|
||||||
|
i = j
|
||||||
|
s.functions <- function
|
||||||
|
|
||||||
|
case token.BlockEnd:
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
return i, errors.New(MissingBlockEnd, file, tokens[i].Position)
|
||||||
|
}
|
|
@ -33,6 +33,8 @@ func (s *scanner) scanFile(path string, pkg string) error {
|
||||||
case token.Comment:
|
case token.Comment:
|
||||||
case token.Identifier:
|
case token.Identifier:
|
||||||
i, err = s.scanFunction(file, tokens, i)
|
i, err = s.scanFunction(file, tokens, i)
|
||||||
|
case token.Extern:
|
||||||
|
i, err = s.scanExtern(file, tokens, i)
|
||||||
case token.Import:
|
case token.Import:
|
||||||
i, err = s.scanImport(file, tokens, i)
|
i, err = s.scanImport(file, tokens, i)
|
||||||
case token.EOF:
|
case token.EOF:
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
|
|
||||||
// scanFunction scans a function.
|
// scanFunction scans a function.
|
||||||
func (s *scanner) scanFunction(file *fs.File, tokens token.List, i int) (int, error) {
|
func (s *scanner) scanFunction(file *fs.File, tokens token.List, i int) (int, error) {
|
||||||
function, i, err := scanSignature(file, tokens, i, token.BlockStart)
|
function, i, err := scanSignature(file, file.Package, tokens, i, token.BlockStart)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return i, err
|
return i, err
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// scanSignature scans only the function signature without the body.
|
// scanSignature scans only the function signature without the body.
|
||||||
func scanSignature(file *fs.File, tokens token.List, i int, delimiter token.Kind) (*core.Function, int, error) {
|
func scanSignature(file *fs.File, pkg string, tokens token.List, i int, delimiter token.Kind) (*core.Function, int, error) {
|
||||||
var (
|
var (
|
||||||
groupLevel = 0
|
groupLevel = 0
|
||||||
nameStart = i
|
nameStart = i
|
||||||
|
@ -83,7 +83,7 @@ func scanSignature(file *fs.File, tokens token.List, i int, delimiter token.Kind
|
||||||
}
|
}
|
||||||
|
|
||||||
name := tokens[nameStart].String(file.Bytes)
|
name := tokens[nameStart].String(file.Bytes)
|
||||||
function := core.NewFunction(name, file)
|
function := core.NewFunction(name, pkg, file)
|
||||||
parameters := tokens[inputStart:inputEnd]
|
parameters := tokens[inputStart:inputEnd]
|
||||||
|
|
||||||
for param := range parameters.Split {
|
for param := range parameters.Split {
|
||||||
|
|
|
@ -22,6 +22,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
var CPU = cpu.CPU{
|
var CPU = cpu.CPU{
|
||||||
Call: []cpu.Register{R0, R7, R6, R2, R10, R8, R9},
|
Call: []cpu.Register{R0, R7, R6, R2, R10, R8, R9},
|
||||||
Syscall: []cpu.Register{R0, R7, R6, R2, R10, R8, R9},
|
Syscall: []cpu.Register{R0, R7, R6, R2, R10, R8, R9},
|
||||||
|
ExternCall: []cpu.Register{R1, R2, R8, R9},
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue