Skip to content

Commit

Permalink
adding most of work to generate assembly and run tests - need to fix …
Browse files Browse the repository at this point in the history
…a bug in gosmeagle to load corpus

Signed-off-by: vsoch <vsoch@users.noreply.github.com>
  • Loading branch information
vsoch committed Oct 27, 2021
1 parent 649ae79 commit 2831926
Show file tree
Hide file tree
Showing 31 changed files with 921 additions and 192 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
smeagleasm
test
9 changes: 9 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM golang:bullseye as gobase
# docker build -t ghcr.io/buildsi/smeagleasm .
FROM ghcr.io/buildsi/smeagle
COPY --from=gobase /usr/local/go/ /usr/local/go/
WORKDIR /src/
COPY . /src/
ENV PATH /usr/local/go/bin:/code/build/standalone:${PATH}
RUN make
ENTRYPOINT ["/bin/bash"]
9 changes: 9 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
all:
gofmt -s -w .
go build -o smeagleasm

build:
go build -o smeagleasm

run:
go run main.go
83 changes: 59 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,27 +26,13 @@ $ ./build/standalone/Smeagle -l /data/libtest.so > /data/smeagle-output.json

This is how I generated the testing [smeagle-output.json](smeagle-output.json)

### 2. Generate Assembly
### 2. Generate Terminal Assembly

Now we can run the program to generate assembly:
For an old Python script, see [scripts](scripts).
Currently we can generate with go:

```bash
$ python load.py smeagle-output.json
```
```
mov $0x4,%rdi
mov $0x3,%rsi
mov $0x5,%rdx
mov $0x3,%rcx
mov $0x1,%r8
mov $0x4,framebase+8
callq bigcall
```

or run with go:

```bash
$ go run main.go smeagle-output.json
$ go run main.go gen smeagle-output.json
```
```
endbr64
Expand All @@ -60,18 +46,67 @@ pushq $0x18
callq bigcall # This is not right for the symbol, obv.
```

And then in the [test](test) folder we can generate assembly for the test binary (which calls this function)
or compile first and run the binary:

```bash
$ make
$ ./smeagleasm gen smeagle-output.json
```

### 3. Codegen + Assembly

What we really want to do for a more robust testing of smeagle is:

1. Use codegen to generate some number of c/c++ scripts that do something in main, matched with a library? The functions should confirm values passed.
2. Compile and generate smeagle output for step 1
3. Run assembly generator scripts here and plug result into a main function template
4. Compile template and run and confirm same answer

#### Codegen

You can use (or add new examples) to [examples](examples). For each example, you should include a codegen.yaml that uses random generation,
and **includes a Makefile to compile some main function to a binary named `binary`, and a library to libfoo.so**

#### Running

Steps 2-4 are done by the library here. Since we also need Smeagle, we do the whole shanbang in a container.

##### 1. Build the container

```bash
$ docker build -t ghcr.io/buildsi/smeagleasm .
```

##### 2. Shell into it

```bash
g++ -S -o test.a test.c
$ docker run -it --rm ghcr.io/buildsi/smeagleasm bash
```

And then arbitrariy delete the function body and add our output above (test.temp)
If you want to bind code locally (e.g., to develop/make changes and then try running):

And try compiling again
```bash
$ docker run -it --rm -v $PWD:/src ghcr.io/buildsi/smeagleasm
```

The working directory, /src has the executable "smeagleasm" and "Smeagle" is in /code/build/standalone.

```bash
g++ -c test.temp -o test.o
ls
Dockerfile README.md cli go.mod libtest.so scripts smeagleasm utils
Makefile asm examples go.sum main.go smeagle-output.json test version
root@59c7c62db9a2:/src# which Smeagle
/code/build/standalone/Smeagle
```

This obviously doesn't work because I don't know what I'm doing :)
Now let's run the test generator!

```bash
$ go run main.go test examples/cpp/simple/codegen.yaml
```

**TODO**

- Corpus Loader needs to skip "empty" functions (e.g., init and fini currently don't add anything)
- Compare output between generated and original
- Try generating smeagle output again?
50 changes: 50 additions & 0 deletions asm/asm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package asm

import (
"fmt"
"math/rand"
"strings"

"github.com/vsoch/gosmeagle/corpus"
)

// Generate assembly from a smeagle output json
func Generate(jsonFile string) string {

// This is output generated from CPP smeagle
c := corpus.Load(jsonFile)
return generateAssembly(&c)
}

// generateAssembly for a corpus, assumed to be a main function for this simple program!
func generateAssembly(c *corpus.LoadedCorpus) string {

output := ""

// This seems to always be printed at the start of main
output += "endbr64\n"
for _, loc := range c.Functions {

// Allocate space on the stack all at once for local variables
spaceBytes := int64(0)
for _, param := range loc.Parameters {
spaceBytes += param.GetSize()
}
output += fmt.Sprintf("subq $%d, %%rsp # Allocate %d bytes of space on the stack for local variables\n", spaceBytes, spaceBytes)
for _, param := range loc.Parameters {
if param.GetClass() == "Integer" {
constant := rand.Intn(100)
// Move constant into rbp+ frameoffset
// mov $0x55 8(%rbp) - 8 changes relative to constant in smeagle fact
// going to be 8 bytes off
if strings.HasPrefix(param.GetLocation(), "framebase") {
output += fmt.Sprintf("pushq $0x%d\n", constant)
} else {
output += fmt.Sprintf("mov $0x%d,%s\n", constant, param.GetLocation())
}
}
}
output += fmt.Sprintf("callq %s\n", loc.Name)
}
return output
}
63 changes: 63 additions & 0 deletions asm/template.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package asm

import (
"bytes"
"github.com/buildsi/smeagleasm/utils"
"html/template"
"log"
"os"
)

// WriteTemplate to a filepath
func WriteTemplate(path string, t *template.Template, content *TemplateContent) {

var buf bytes.Buffer
if err := t.Execute(&buf, content); err != nil {
log.Fatalf("Cannot write template to buffer: %x", err)
}
utils.WriteFile(path, buf.String())
}

// WriteFile and some content to the filesystem
func WriteFile(path string, content string) error {

filey, err := os.Create(path)
if err != nil {
return err
}
defer filey.Close()

_, err = filey.WriteString(content)
if err != nil {
return err
}
err = filey.Sync()
return nil
}

type TemplateContent struct {
Call string
}

// Template with empty "main" function to generate
func getTemplate() string {
return `.file "test.c"
.text
.globl main
.type main, @function
main:
.LFB0:
endbr64
pushq %rbp
movq %rsp, %rbp
{{ .Call }}
addq $16, %rsp
movl $0, %eax
leave
ret
.LFE0:
.size main, .-main
.ident "Dinosaur Produced"`
}
90 changes: 90 additions & 0 deletions asm/test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package asm

import (
"fmt"
"html/template"
"io/ioutil"
"log"
// "os"
"path/filepath"

"github.com/buildsi/codegen/generate"
"github.com/buildsi/smeagleasm/utils"
)

// Run tests with a codegen.yaml
func RunTests(jsonYaml string, n int) {

// Crete temporary outdir in /tmp
tmpdir, err := ioutil.TempDir("", "smeagle-asm")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Writing tests to %s\n", tmpdir)
//defer os.RemoveAll(tmpdir)

// generate code in output directory
generate.Generate(jsonYaml, tmpdir, "random")

// If we don't have a Makefile, cannot build
testdir := filepath.Join(tmpdir, "1")
makefile := filepath.Join(testdir, "Makefile")
if !utils.Exists(makefile) {
log.Fatalf("Cannot build tests without Makefile, %s does not exist!\n", makefile)
}

// System commands to run makefile, changing to testdir
_, err = utils.RunCommand([]string{"make"}, []string{}, testdir, "")
if err != nil {
log.Fatalf("Error compiling Makefile for test: %s\n", err)
}

// A binary must exist named binary
binary := filepath.Join(testdir, "binary")
libfoo := filepath.Join(testdir, "libfoo.so")
if !utils.Exists(binary) || !utils.Exists(libfoo) {
log.Fatalf("Expected output binary 'binary' or 'libfoo.so' does not exist!\n")
}

// generate smeagle output for the binary (Smeagle must be on path!)
outfile := filepath.Join(testdir, "smeagle-output.json")
_, err = utils.RunCommand([]string{"Smeagle", "-l", libfoo}, []string{}, testdir, outfile)
if err != nil {
log.Fatalf("Error running Smeagle!: %s!\n", err)
}

// Read in smeagle output and generate assembly
assembly := Generate(outfile)
content := TemplateContent{assembly}

// Template a second binary, compile again
t := template.Must(template.New("binary").Parse(getTemplate()))

assemblyFile := filepath.Join(testdir, "binary.s")
WriteTemplate(assemblyFile, t, &content)
if !utils.Exists(assemblyFile) {
log.Fatalf("Cannot write template to assembly file!\n")
}

// Compile again
_, err = utils.RunCommand([]string{"g++", "binary.s", "-L.", "-ltest", "-o", "edits"}, []string{}, testdir, "")
if err != nil {
log.Fatalf("Error compiling second assembly file: %s\n", err)
}

// Run both and compare result - should run in container at this point
resOriginal, err := utils.RunCommand([]string{"./binary"}, []string{}, testdir, "")
if err != nil {
log.Fatalf("Issue running original binary %x\n", err)
}

resEdited, err := utils.RunCommand([]string{"./edits"}, []string{}, testdir, "")
if err != nil {
log.Fatalf("Issue running assembly-generated binary %x\n", err)
}

// print result here / calculate metrics!
fmt.Println(resOriginal, resEdited)

// Can we generate smeagle assembly again?
}
26 changes: 26 additions & 0 deletions cli/cli.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package cli

import (
"github.com/DataDrake/cli-ng/v2/cmd"
"github.com/buildsi/smeagleasm/version"
)

// GlobalFlags contains the flags for commands.
type GlobalFlags struct{}

// Root is the main command.
var Root *cmd.Root

// init create the root command and creates subcommands
func init() {
Root = &cmd.Root{
Name: "smeagleasm",
Short: "Testing utility for Smeagle to generate assembly via Go",
Version: version.Version,
Copyright: "© 2021 Vanessa Sochat <@vsoch>",
License: "Licensed under a combined LICENSE",
}
cmd.Register(&cmd.Help)
cmd.Register(&cmd.Version)
cmd.Register(&cmd.GenManPages)
}
35 changes: 35 additions & 0 deletions cli/gen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package cli

import (
"fmt"
"github.com/DataDrake/cli-ng/v2/cmd"
"github.com/buildsi/smeagleasm/asm"
)

// Args and flags for generate
type GenArgs struct {
JsonFile []string `desc:"smeagle json to generate assembly for"`
}

type GenFlags struct{}

// Parser looks at symbols and ABI in Go
var Generator = cmd.Sub{
Name: "gen",
Alias: "g",
Short: "generate assembly from smeagle output",
Flags: &GenFlags{},
Args: &GenArgs{},
Run: RunGen,
}

func init() {
cmd.Register(&Generator)
}

// RunParser reads a file and creates a corpus
func RunGen(r *cmd.Root, c *cmd.Sub) {
args := c.Args.(*GenArgs)
assembly := asm.Generate(args.JsonFile[0])
fmt.Println(assembly)
}

0 comments on commit 2831926

Please sign in to comment.