Go и MISP32

Официально компилятор Go не поддерживает кросскомпиляцию в архитектуру MIPS32, но если очень хочется, то решение можно найти.

Если не требуется свежая версия Go, то существует форк компилятора версии 1.4 с поддержкой MIPS32.

Сборка и тестирование

В первую очередь забираем себе исходники.

mkdir -p ~/src
cd ~/src
git clone https://github.com/gomini/go-mips32.git

Собираем компилятор c поддержкой конкретной архитектуры. Учтите, что компилятор будет собран для запуске на вашей машине, но будет выдавать код для указанной архитектуры.

cd go-mips32/src
GOOS=linux GOARCH=mips32 ./make.bash

Если возникают ошибки компиляции1), то может помочь добавление двух переменных окружения:

CGO_ENABLED=0 CC=gcc-4.9

С мипсами есть тонкость: существует две вариации BigEndian (BE) и LessEndian (LE). Собственно разница только в представлении многобайтных чисел, но надо учитывать для какого процессора вы будете собирать свои приложения. Так семейство ar71xx требует BE (и переменной окружения GOARCH=mips32), а семейство ramips требует LE и переменную окружения GOARCH=mips32le.

Так что можно собрать компилятор и под вторую архитектуру.

GOOS=linux GOARCH=mips32le ./make.bash

Отлично. Компилятор собран и мы можем его проверить

Для этого создадим где нибудь файл hello.go

package main
 
import (
	"fmt"
	"time"
)
 
func main() {
	fmt.Println("Hello world")
	fmt.Println("Current time ", time.Now().Format(time.UnixDate))
}

Соберём его под MIPS32LE:

GOROOT=~/src/go-mips32 GOOS=linux GOARCH=mips32le ~/src/go-mips32/bin/go build hello.go 

И посмотрим, что получилось:

$ file hello
hello: ELF 32-bit LSB executable, MIPS, MIPS32 rel2 version 1 (SYSV), statically linked, not stripped

Аналогично под MIPS32BE:

$ GOROOT=~/src/go-mips32 GOOS=linux GOARCH=mips32 ~/src/go-mips32/bin/go build hello.go 
$ file hello
hello: ELF 32-bit MSB executable, MIPS, MIPS32 rel2 version 1 (SYSV), statically linked, not stripped

Ещё раз обратите внимание, что разница только в GOARCH в одном случае mips32 а в другом mips32le.

Полученный исполнимый файл мы можем скопировать на роутер. В моём случае это «народный» TP-Link WR703N с установленной прошивкой OpenWrt (Прошивка OpenWRT на TP-Link WR703N):

$ scp hello root@192.168.1.1:/tmp/

Как заметил внимательный читатель, копируем мы его в RAM-диск, поскольку в стандартный SPI-flash роутера файл не влезет (почти 2Мб). И это минимальное приложение. Связано это с тем, что Go линкует всё что только можно в один исполняемый файл. Как уменьшить размер файла мы поговорим позже.

Зайдя на роутер по SSH запустим свежий бинарник.

# /tmp/hello 
Hello world
Current time  Thu Aug 25 10:09:47 UTC 2016

Плюсы:

  • За короткое время мы получили готовый бинарный файл, который наверняка запустится на любых MIPS32BE машинах под управлением OpenWrt и с высокой вероятностью, под управлением других систем на базе ядра Linux.

Минусы:

  • Размер получаемого файла. Загрузить его в среднестатистический роутер без расширения памяти внешним USB диском, или перепайкой встроенной SPI-flash памяти - задача не выполнимая.

Как уменьшить размер файла

Проблема уменьшения размеров бинарников полученных из Go волнует многих разработчиков, и о ней написано много статей. Например Binary Sizes in Go. Её за основу мы и возьмём.

Рассмотрим пример простого вебсервера web-server.go:

package main
 
import (
	"log"
	"net/http"
)
 
type Handler struct {
	Body []byte
}
 
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Write(h.Body)
}
 
func main() {
	handler := Handler{
		Body: []byte("Hello World\n"),
	}
 
	log.Println("listening on :8000")
	err := http.ListenAndServe(":8000", handler)
	if err != nil {
		log.Println("fatal:", err)
	}
}

Что бы упростить процедуру сборки сразу пропишем переменные окружения для использования нашего компилятора:

export GOROOT=~/src/go-mips32
export GOOS=linux
export GOARCH=mips32
export PATH=$HOME/src/go-mips32/bin:$PATH

Теперь можно собирать приложение лёгким движением руки:

$ go build web-server.go 
$ file web-server
web-server: ELF 32-bit MSB executable, MIPS, MIPS32 rel2 version 1 (SYSV), statically linked, not stripped

Отлично! То что нужно! Вот только размер не радует:

$ ls -lh web-server
-rwxr-xr-x 1 ivan ivan 4,9M авг 25 14:04 web-server

Почти 5 мегабайт, это больше чем весь флеш среднестатического роутера.

Пересоберём бинарник без отладочной инфорамации:

$ go build -ldflags="-w" web-server.go 
$ ls -lh web-server
-rwxr-xr-x 1 ivan ivan 3,8M авг 25 14:08 web-server

Следующим шагом воспользуемся утилитой strip, но не простой, а заточенной под нашу архитектуру. В дебиан это делается лёгким движением руки:

sudo apt-get install binutils-mips-linux-gnu binutils-mipsel-linux-gnu

Конечно, если интересен только один из мипсов, можно и пакет ставить только один.

Теперь можно ещё подрезать наш бинарник:

$ mips-linux-gnu-strip web-server
$ ls -lh web-server
-rwxr-xr-x 1 ivan ivan 3,5M авг 25 14:13 web-server

Уменьшение размера уже не столько впечатляет, но 300Кб тоже хлеб.

И напоследок воспользуемся утилитой http://upx.sourceforge.net/, которая позволяет сжимать исполняемые файлы.

Установить в Debian её можно командой sudo apt-get install upx-ucl.

Жмём с самой сильной компрессией:

$ upx -9 web-server
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2013
UPX 3.91        Markus Oberhumer, Laszlo Molnar & John Reiser   Sep 30th 2013
 
        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
   3646152 ->   1109476   30.43%  linux/mipseb   web-server                    
 
Packed 1 file.

Получили более чем трёхкратное уменьшение размера файла.

Конечно 1Мб, всё ещё достаточно велик для роутера, да и это практически нижний предел, для чего-то полезного, но грамотный электронщик будет работать с микро-компьютерами с увеличенной памятью или размещать приложения на внешнем USB/SD диске.

Кстати, даже если размещать бинарник на внешнем диске большого объёма упаковка файла имеет смысл: распаковка в оперативную память займёт времени меньше, чем чтение с медленного внешнего устройства.

Следует иметь в виду, что любая из выше приведённых операций может сделать программу неработоспособной. Например, если программа использует рефлексию.

Настройка рабочего окружения

Для полноценной работы: удобной компиляции, установки дополнительных пакетов настроим корректно переменные окружения.

Некоторые рекомендуют скопировать ~/srg/go-mips32 в /opt. Так вот, этого делать не стоит, go get после этого не работает нормально.

Создадим отдельный каталог для окружения ($GOPATH):

mkdir -p ~/go-mips32/

А в нём файл set_env.sh со следующим содержимым.

# Setup architectures
export GOOS=linux
export GOARCH=mips32
 
# Go pathes
export GOROOT=$HOME/src/go-mips32
export GOPATH=$HOME/go-mips32
 
# Path to go binaries
export PATH=$GOROOT/bin:$PATH

Теперь можно в любой момент переключиться на наш компилятор.

. set_env.sh

Обратите внимание, что здесь именно точка пробел имя файла2).

И в качестве финального штриха установим веб-фреймворк Gin Gonic и соберём маленькое приложение на его основе.

Создадим каталог для нашего приложения:

mkdir -p $GOPATH/src/test
cd $GOPATH/src/test

Создадим в этом каталоге go-файл gin-server.go со следующим содержимым:

package main
 
import (
	"time"
	"github.com/gin-gonic/gin"
)
 
func main() {
	r := gin.Default()
	r.GET("/", func(c *gin.Context) {
		c.String(200, "Now "+time.Now().Format(time.UnixDate))
	})
	r.Run() // listen and serve on 0.0.0.0:8080
}

Получим пакет фреймворка:

go get -u -v github.com/gin-gonic/gin

Это займёт достаточно длительное время, поскольку будут скомпилированны и установлены сам пакет и его зависимости.

И наконец соберём наше приложение:

$ go build  gin-server.go 
$ file gin-server
gin-server: ELF 32-bit MSB executable, MIPS, MIPS32 rel2 version 1 (SYSV), statically linked, not stripped
$ ls -lh gin-server
-rwxr-xr-x 1 ivan ivan 7,8M авг 26 00:32 gin-server

1) У меня это произошло на Debian testing c gcc-6
2) Таким образом происходит переменных окружения к текущей консольной сессии