Test enhancement (#133)
* test-enhancement * test-enhancement * test-enhancement test-enhancement test-enhancement test-enhancement test-enhancement test-enhancement test-enhancement test-enhancement test-enhancement test-enhancement test-enhancement test-enhancement test-enhancement test-enhancement test-enhancement modify get_started modify get started modify get started modify * modify base_test to resolve conflicts * test-tmp * modify config * modify config * imeplement nulllogger to bypass go vet check * add test for du * add test for count * use make to generate docs * replace hook with jmp to enable closure go fmt * verify-bug * unhook exec after tests to facilitate following tests * wrap unhook to pass golint check
This commit is contained in:
parent
0c379995f0
commit
81006179dc
12
.travis.yml
12
.travis.yml
|
@ -6,18 +6,26 @@ matrix:
|
|||
os:
|
||||
- linux
|
||||
go_import_path: github.com/fluid-cloudnative/fluid
|
||||
sudo: false
|
||||
#for the convenience of gohook to set gcflag here
|
||||
env:
|
||||
- TEST_FLAGS='-race -coverprofile=coverage.txt -covermode=atomic -gcflags=-l'
|
||||
sudo: true
|
||||
before_script:
|
||||
- curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin latest
|
||||
- arch=$(go env GOARCH)
|
||||
- curl -L https://go.kubebuilder.io/dl/2.3.1/linux/${arch} | tar -xz -C /tmp/
|
||||
- sudo mv /tmp/kubebuilder_2.3.1_linux_${arch} /usr/local/kubebuilder
|
||||
- export PATH=$PATH:/usr/local/kubebuilder/bin
|
||||
script:
|
||||
- CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=off go build -o bin/manager cmd/controller/main.go
|
||||
- CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=off go build -o bin/csi cmd/csi/main.go
|
||||
- CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=off go vet ./...
|
||||
- golangci-lint run --timeout=10m ./...
|
||||
- test -z "$(go fmt ./... 2>/dev/null | tee /dev/stderr)" || (echo "please format Go code with 'gofmt'")
|
||||
- TEST_FLAGS='-race -coverprofile=coverage.txt -covermode=atomic' make unit-test
|
||||
- go test ./... ${TEST_FLAGS}
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
|
||||
- language: ruby
|
||||
rvm:
|
||||
- 2.6
|
||||
|
|
3
Makefile
3
Makefile
|
@ -28,6 +28,7 @@ all: manager
|
|||
test: generate fmt vet manifests
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=off go list ./... | grep -v controller | xargs go test -coverprofile cover.out
|
||||
|
||||
|
||||
# used in CI and simply ignore controller tests which need k8s now.
|
||||
# maybe incompatible if more end to end tests are added.
|
||||
unit-test: generate fmt vet manifests
|
||||
|
@ -72,7 +73,7 @@ fmt:
|
|||
|
||||
# Run go vet against code
|
||||
vet:
|
||||
GO111MODULE=off go vet ./...
|
||||
GO111MODULE=off go list ./... | grep -v "vendor" | xargs go vet
|
||||
|
||||
# Generate code
|
||||
generate: controller-gen
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
api:
|
||||
cp zh/dev/api_doc.md zh/dev/api_doc.html;
|
||||
merge:
|
||||
python3 scripts/mergeByTOC.py zh/
|
||||
python3 scripts/mergeByTOC.py en/
|
||||
md2pdf:merge
|
||||
./scripts/genDoc.sh
|
||||
api2pdf:api
|
||||
xvfb-run wkhtmltopdf zh/dev/api_doc.html api.pdf
|
||||
build:md2pdf api2pdf
|
||||
python3 scripts/mergePDF.py zh;
|
||||
python3 scripts/mergePDF.py en
|
||||
clean:
|
||||
rm api.pdf;
|
||||
rm output*.pdf;
|
||||
rm en/doc.md;rm zh/doc.md;
|
||||
rm zh/dev/api_doc.html;
|
||||
|
|
@ -20,8 +20,12 @@ Before generating,we suppose you have installed [Docker](https://www.docker.com/
|
|||
don't have to install required tools one by one.
|
||||
|
||||
1. Download Required Docker Image
|
||||
`docker pull registry.cn-hangzhou.aliyuncs.com/docs-fluid/doc-build `
|
||||
`docker pull registry.cn-hangzhou.aliyuncs.com/docs-fluid/doc-build `
|
||||
2. Start a Container
|
||||
`docker run -it -v <your fluid/docs path>:/data/ fluid/doc-build:0.2.0`
|
||||
3. Run Build script
|
||||
`data/bluid.sh`
|
||||
`docker run -it -v <your fluid/docs path>:/data/ fluid/doc-build:0.2.0`
|
||||
3. Run Makefile
|
||||
```shell
|
||||
cd data
|
||||
make build
|
||||
make clean
|
||||
```
|
|
@ -18,8 +18,12 @@
|
|||
目前我们提供了脚本以便用户自行生成PDF格式的文档。为了避免你配置生成环境,我们提供了Docker镜像,所以生成文档前,请确认你安装
|
||||
了[Docker](https://www.docker.com/)。
|
||||
1. 获取Docker镜像
|
||||
`docker pull registry.cn-hangzhou.aliyuncs.com/docs-fluid/doc-build `
|
||||
2. 创建容器
|
||||
`docker run -it -v <your fluid/docs path>:/data/ fluid/doc-build:0.2.0`
|
||||
3. 执行脚本
|
||||
`data/bluid.sh`
|
||||
`docker pull registry.cn-hangzhou.aliyuncs.com/docs-fluid/doc-build `
|
||||
2. 创建容器
|
||||
`docker run -it -v <your fluid/docs path>:/data/ fluid/doc-build:0.2.0`
|
||||
3. 执行Makefile
|
||||
```shell
|
||||
cd data
|
||||
make build
|
||||
make clean
|
||||
```
|
||||
|
|
|
@ -53,7 +53,8 @@ This document mainly describes how to deploy Fluid with Helm, and use Fluid to c
|
|||
Fluid provides cloud-native data acceleration and management capabilities, and use *dataset* as a high-level abstraction to facilitate user management. Here we will show you how to create a dataset with Fluid.
|
||||
|
||||
1. Create a Dataset object through the CRD file, which describes the source of the dataset.
|
||||
```yaml
|
||||
```shell
|
||||
$ cat<<EOF >dataset.yaml
|
||||
apiVersion: data.fluid.io/v1alpha1
|
||||
kind: Dataset
|
||||
metadata:
|
||||
|
@ -62,15 +63,16 @@ Fluid provides cloud-native data acceleration and management capabilities, and u
|
|||
mounts:
|
||||
- mountPoint: https://mirror.bit.edu.cn/apache/spark/spark-3.0.0/
|
||||
name: spark
|
||||
EOF
|
||||
```
|
||||
Create dataset with kubectl
|
||||
|
||||
```shell
|
||||
kubectl create -f dataset.yaml
|
||||
```
|
||||
|
||||
2. Create an `AlluxioRuntime` CRD object to support the dataset we created. We use [Alluxio](https://www.alluxio.io/) as its runtime here.
|
||||
```yaml
|
||||
```shell
|
||||
$ cat<<EOF >runtime.yaml
|
||||
apiVersion: data.fluid.io/v1alpha1
|
||||
kind: AlluxioRuntime
|
||||
metadata:
|
||||
|
@ -106,6 +108,7 @@ Fluid provides cloud-native data acceleration and management capabilities, and u
|
|||
args:
|
||||
- fuse
|
||||
- --fuse-opts=direct_io,ro,max_read=131072
|
||||
EOF
|
||||
```
|
||||
|
||||
Create *Alluxio* Runtime with `kubectl`
|
||||
|
@ -116,7 +119,8 @@ Fluid provides cloud-native data acceleration and management capabilities, and u
|
|||
|
||||
3. Next, we create an application to access this dataset. Here we will access the same data multiple times and compare the time consumed by each access.
|
||||
|
||||
```yaml
|
||||
```shell
|
||||
$ cat<<EOF >app.yaml
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
|
@ -132,20 +136,21 @@ Fluid provides cloud-native data acceleration and management capabilities, and u
|
|||
- name: demo
|
||||
persistentVolumeClaim:
|
||||
claimName: demo
|
||||
EOF
|
||||
```
|
||||
|
||||
Create Pod with `kubectl`
|
||||
|
||||
```shell
|
||||
kubectl create -f app.yaml
|
||||
$ kubectl create -f app.yaml
|
||||
```
|
||||
|
||||
4. Dive into the container to access data, the first access will take longer.
|
||||
```
|
||||
kubectl exec -it demo-app -- bash
|
||||
# du -sh /data/spark/spark-3.0.0-bin-without-hadoop.tgz
|
||||
$ kubectl exec -it demo-app -- bash
|
||||
$ du -sh /data/spark/spark-3.0.0-bin-without-hadoop.tgz
|
||||
150M /data/spark/spark-3.0.0-bin-without-hadoop.tgz
|
||||
# time cp /data/spark/spark-3.0.0-bin-without-hadoop.tgz /dev/null
|
||||
$ time cp /data/spark/spark-3.0.0-bin-without-hadoop.tgz /dev/null
|
||||
real 0m13.171s
|
||||
user 0m0.002s
|
||||
sys 0m0.028s
|
||||
|
@ -153,9 +158,9 @@ Fluid provides cloud-native data acceleration and management capabilities, and u
|
|||
|
||||
5. In order to avoid the influence of other factors like page cache, we will delete the previous container, create the same application, and try to access the same file. Since the file has been cached by alluxio at this time, you can see that it takes significantly less time now.
|
||||
```
|
||||
kubectl delete -f app.yaml && kubectl create -f app.yaml
|
||||
...
|
||||
# time cp /data/spark/spark-3.0.0-bin-without-hadoop.tgz /dev/null
|
||||
$ kubectl delete -f app.yaml && kubectl create -f app.yaml
|
||||
$ kubectl exec -it demo-app -- bash
|
||||
$ time cp /data/spark/spark-3.0.0-bin-without-hadoop.tgz /dev/null
|
||||
real 0m0.344s
|
||||
user 0m0.002s
|
||||
sys 0m0.020s
|
||||
|
|
|
@ -53,7 +53,8 @@
|
|||
Fluid提供了云原生的数据加速和管理能力,并抽象出了`数据集(Dataset)`概念方便用户管理,接下来将演示如何用 Fluid 创建一个数据集。
|
||||
|
||||
1. 创建一个Dataset CRD对象,其中描述了数据集的来源。
|
||||
```yaml
|
||||
```shell
|
||||
$ cat<<EOF >dataset.yaml
|
||||
apiVersion: data.fluid.io/v1alpha1
|
||||
kind: Dataset
|
||||
metadata:
|
||||
|
@ -62,15 +63,17 @@ Fluid提供了云原生的数据加速和管理能力,并抽象出了`数据
|
|||
mounts:
|
||||
- mountPoint: https://mirror.bit.edu.cn/apache/spark/spark-3.0.0/
|
||||
name: spark
|
||||
EOF
|
||||
```
|
||||
执行安装
|
||||
|
||||
```
|
||||
kubectl create -f dataset.yaml
|
||||
$ kubectl create -f dataset.yaml
|
||||
```
|
||||
|
||||
2. 创建 `AlluxioRuntime` CRD对象,用来描述支持这个数据集的 Runtime, 在这里我们使用[Alluxio](https://www.alluxio.io/)作为其Runtime
|
||||
```yaml
|
||||
```shell
|
||||
$ cat<<EOF >runtime.yaml
|
||||
apiVersion: data.fluid.io/v1alpha1
|
||||
kind: AlluxioRuntime
|
||||
metadata:
|
||||
|
@ -106,15 +109,17 @@ Fluid提供了云原生的数据加速和管理能力,并抽象出了`数据
|
|||
args:
|
||||
- fuse
|
||||
- --fuse-opts=direct_io,ro,max_read=131072
|
||||
EOF
|
||||
```
|
||||
使用`kubectl`完成创建
|
||||
|
||||
```shell
|
||||
kubectl create -f runtime.yaml
|
||||
$ kubectl create -f runtime.yaml
|
||||
```
|
||||
|
||||
3. 接下来,我们创建一个应用容器来使用该数据集,我们将多次访问同一数据,并比较访问时间来展示 Fluid 的加速效果。
|
||||
```yaml
|
||||
```shell
|
||||
$ cat<<EOF >app.yaml
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
|
@ -130,14 +135,20 @@ Fluid提供了云原生的数据加速和管理能力,并抽象出了`数据
|
|||
- name: demo
|
||||
persistentVolumeClaim:
|
||||
claimName: demo
|
||||
EOF
|
||||
```
|
||||
使用`kubectl`完成创建
|
||||
|
||||
```shell
|
||||
$ kubectl create -f app.yaml
|
||||
```
|
||||
|
||||
4. 登录到应用容器中访问数据,初次访问会花费更长时间。
|
||||
```shell
|
||||
kubectl exec -it demo-app -- bash
|
||||
# du -sh /data/spark/spark-3.0.0-bin-without-hadoop.tgz
|
||||
$ kubectl exec -it demo-app -- bash
|
||||
$ du -sh /data/spark/spark-3.0.0-bin-without-hadoop.tgz
|
||||
150M /data/spark/spark-3.0.0-bin-without-hadoop.tgz
|
||||
# time cp /data/spark/spark-3.0.0-bin-without-hadoop.tgz /dev/null
|
||||
$ time cp /data/spark/spark-3.0.0-bin-without-hadoop.tgz /dev/null
|
||||
real 0m13.171s
|
||||
user 0m0.002s
|
||||
sys 0m0.028s
|
||||
|
@ -145,9 +156,9 @@ Fluid提供了云原生的数据加速和管理能力,并抽象出了`数据
|
|||
|
||||
5. 为了避免其他因素(比如 page cache )对结果造成影响,我们将删除之前的容器,新建相同的应用,尝试访问同样的文件。由于此时文件已经被 `Alluxio` 缓存,可以看到第二次访问所需时间远小于第一次。
|
||||
```shell
|
||||
kubectl delete -f app.yaml && kubectl create -f app.yaml
|
||||
...
|
||||
# time cp /data/spark/spark-3.0.0-bin-without-hadoop.tgz /dev/null
|
||||
$ kubectl delete -f app.yaml && kubectl create -f app.yaml
|
||||
$ kubectl exec -it demo-app -- bash
|
||||
$ time cp /data/spark/spark-3.0.0-bin-without-hadoop.tgz /dev/null
|
||||
real 0m0.344s
|
||||
user 0m0.002s
|
||||
sys 0m0.020s
|
||||
|
|
1
go.mod
1
go.mod
|
@ -3,6 +3,7 @@ module github.com/fluid-cloudnative/fluid
|
|||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/brahma-adshonor/gohook v1.1.9
|
||||
github.com/container-storage-interface/spec v1.2.0
|
||||
github.com/docker/go-units v0.4.0
|
||||
github.com/go-logr/logr v0.1.0
|
||||
|
|
4
go.sum
4
go.sum
|
@ -57,6 +57,8 @@ github.com/bifurcation/mint v0.0.0-20180715133206-93c51c6ce115/go.mod h1:zVt7zX3
|
|||
github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
|
||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||
github.com/brahma-adshonor/gohook v1.1.9 h1:YuQFj8rhAj1kvtGHUc5BlLvAELw98M/ydpBz/MvBXNU=
|
||||
github.com/brahma-adshonor/gohook v1.1.9/go.mod h1:3B9f7Lwh7z5fW6MvUvGxCFaVdloVI+1tOsl4BMJdWr0=
|
||||
github.com/caddyserver/caddy v1.0.3/go.mod h1:G+ouvOY32gENkJC+jhgl62TyhvqEsFaDiZ4uw0RzP1E=
|
||||
github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
|
@ -530,6 +532,8 @@ go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/
|
|||
go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||
golang.org/x/arch v0.0.0-20190312162104-788fe5ffcd8c h1:Rx/HTKi09myZ25t1SOlDHmHOy/mKxNAcu0hP1oPX9qM=
|
||||
golang.org/x/arch v0.0.0-20190312162104-788fe5ffcd8c/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4=
|
||||
golang.org/x/build v0.0.0-20190927031335-2835ba2e683f/go.mod h1:fYw7AShPAhGMdXqA9gRadk/CcMsvLlClpE5oBwnS3dM=
|
||||
golang.org/x/crypto v0.0.0-20180426230345-b49d69b5da94/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
|
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
|||
package alluxio
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
|
@ -39,6 +40,7 @@ import (
|
|||
var cfg *rest.Config
|
||||
var k8sClient client.Client
|
||||
var testEnv *envtest.Environment
|
||||
var useExistingCluster = false
|
||||
|
||||
func TestAPIs(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
|
@ -50,6 +52,9 @@ func TestAPIs(t *testing.T) {
|
|||
|
||||
var _ = BeforeSuite(func(done Done) {
|
||||
logf.SetLogger(zap.LoggerTo(GinkgoWriter, true))
|
||||
if env := os.Getenv("USE_EXISTING_CLUSTER"); env == "true" {
|
||||
useExistingCluster = true
|
||||
}
|
||||
|
||||
By("bootstrapping test environment")
|
||||
testEnv = &envtest.Environment{
|
||||
|
|
|
@ -17,6 +17,7 @@ package dataset
|
|||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
|
@ -44,6 +45,7 @@ var cfg *rest.Config
|
|||
var k8sClient client.Client
|
||||
var testEnv *envtest.Environment
|
||||
var testCtx = context.Background()
|
||||
var useExistingCluster = false
|
||||
|
||||
func TestAPIs(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
|
@ -55,10 +57,13 @@ func TestAPIs(t *testing.T) {
|
|||
|
||||
var _ = BeforeSuite(func(done Done) {
|
||||
logf.SetLogger(zap.LoggerTo(GinkgoWriter, true))
|
||||
|
||||
if env := os.Getenv("USE_EXISTING_CLUSTER"); env != "" {
|
||||
useExistingCluster = true
|
||||
}
|
||||
By("bootstrapping test environment")
|
||||
testEnv = &envtest.Environment{
|
||||
CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "..", "config", "crd", "bases")},
|
||||
UseExistingCluster: &useExistingCluster,
|
||||
CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "..", "config", "crd", "bases")},
|
||||
}
|
||||
|
||||
var err error
|
||||
|
|
|
@ -56,7 +56,7 @@ func (a AlluxioFileUtils) IsExist(alluxioPath string) (found bool, err error) {
|
|||
if strings.Contains(stdout, "does not exist") {
|
||||
err = nil
|
||||
} else {
|
||||
err = fmt.Errorf("execute command %v with err: %v stdout %s and stderr %s", command, err, stdout, stderr)
|
||||
err = fmt.Errorf("execute command %v with expectedErr: %v stdout %s and stderr %s", command, err, stdout, stderr)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
|
@ -87,7 +87,7 @@ func (a AlluxioFileUtils) LoadMetaData(alluxioPath string, sync bool) (err error
|
|||
duration := time.Since(start)
|
||||
a.log.Info("Load MetaData took times to run", "period", duration)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("execute command %v with err: %v stdout %s and stderr %s", command, err, stdout, stderr)
|
||||
err = fmt.Errorf("execute command %v with expectedErr: %v stdout %s and stderr %s", command, err, stdout, stderr)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -103,7 +103,7 @@ func (a AlluxioFileUtils) Mkdir(alluxioPath string) (err error) {
|
|||
|
||||
stdout, stderr, err = a.exec(command, false)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("execute command %v with err: %v stdout %s and stderr %s", command, err, stdout, stderr)
|
||||
err = fmt.Errorf("execute command %v with expectedErr: %v stdout %s and stderr %s", command, err, stdout, stderr)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -116,15 +116,15 @@ func (a AlluxioFileUtils) Mount(alluxioPath string,
|
|||
readOnly bool,
|
||||
shared bool) (err error) {
|
||||
|
||||
// exist, err := a.IsExist(alluxioPath)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// exist, expectedErr := a.IsExist(alluxioPath)
|
||||
// if expectedErr != nil {
|
||||
// return expectedErr
|
||||
// }
|
||||
|
||||
// if !exist {
|
||||
// err = a.Mkdir(alluxioPath)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// expectedErr = a.Mkdir(alluxioPath)
|
||||
// if expectedErr != nil {
|
||||
// return expectedErr
|
||||
// }
|
||||
// }
|
||||
|
||||
|
@ -150,7 +150,7 @@ func (a AlluxioFileUtils) Mount(alluxioPath string,
|
|||
|
||||
stdout, stderr, err = a.exec(command, false)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("execute command %v with err: %v stdout %s and stderr %s", command, err, stdout, stderr)
|
||||
err = fmt.Errorf("execute command %v with expectedErr: %v stdout %s and stderr %s", command, err, stdout, stderr)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -166,7 +166,7 @@ func (a AlluxioFileUtils) IsMounted(alluxioPath string) (mounted bool, err error
|
|||
|
||||
stdout, stderr, err = a.exec(command, true)
|
||||
if err != nil {
|
||||
return mounted, fmt.Errorf("execute command %v with err: %v stdout %s and stderr %s", command, err, stdout, stderr)
|
||||
return mounted, fmt.Errorf("execute command %v with expectedErr: %v stdout %s and stderr %s", command, err, stdout, stderr)
|
||||
}
|
||||
|
||||
results := strings.Split(stdout, "\n")
|
||||
|
@ -211,7 +211,7 @@ func (a AlluxioFileUtils) Du(alluxioPath string) (ufs int64, cached int64, cache
|
|||
|
||||
stdout, stderr, err = a.exec(command, false)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("execute command %v with err: %v stdout %s and stderr %s", command, err, stdout, stderr)
|
||||
err = fmt.Errorf("execute command %v with expectedErr: %v stdout %s and stderr %s", command, err, stdout, stderr)
|
||||
return
|
||||
}
|
||||
str := strings.Split(stdout, "\n")
|
||||
|
@ -254,7 +254,7 @@ func (a AlluxioFileUtils) Count(alluxioPath string) (fileCount int64, folderCoun
|
|||
|
||||
stdout, stderr, err = a.exec(command, false)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("execute command %v with err: %v stdout %s and stderr %s", command, err, stdout, stderr)
|
||||
err = fmt.Errorf("execute command %v with expectedErr: %v stdout %s and stderr %s", command, err, stdout, stderr)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -16,12 +16,34 @@ limitations under the License.
|
|||
package operations
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/brahma-adshonor/gohook"
|
||||
"github.com/fluid-cloudnative/fluid/pkg/utils/kubeclient"
|
||||
"github.com/go-logr/logr"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
NOT_EXIST = "not-exist"
|
||||
OTHER_ERR = "other-err"
|
||||
FINE = "fine"
|
||||
EXEC_ERR = "exec-err"
|
||||
TOO_MANY_LINES = "too many lines"
|
||||
DATA_NUM = "data nums not match"
|
||||
PARSE_ERR = "parse err"
|
||||
)
|
||||
|
||||
// a empty logger just for testing ...
|
||||
type NullLogger struct{}
|
||||
|
||||
func (_ NullLogger) Info(_ string, _ ...interface{}) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
func TestLoadMetaData(t *testing.T) {
|
||||
var tests = []struct {
|
||||
path string
|
||||
|
@ -37,9 +59,186 @@ func TestLoadMetaData(t *testing.T) {
|
|||
for _, test := range tests {
|
||||
tools := NewAlluxioFileUtils("", "", "", ctrl.Log)
|
||||
err := tools.LoadMetaData(test.path, test.sync)
|
||||
// fmt.Println(err)
|
||||
// fmt.Println(expectedErr)
|
||||
if err == nil {
|
||||
t.Errorf("expected %v, got %v", test.path, tools)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (_ NullLogger) Enabled() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (_ NullLogger) Error(_ error, _ string, _ ...interface{}) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
func (log NullLogger) V(_ int) logr.InfoLogger {
|
||||
return log
|
||||
}
|
||||
|
||||
func (log NullLogger) WithName(_ string) logr.Logger {
|
||||
return log
|
||||
}
|
||||
|
||||
func (log NullLogger) WithValues(_ ...interface{}) logr.Logger {
|
||||
return log
|
||||
}
|
||||
|
||||
//imeplement nulllogger to bypass go vet check
|
||||
|
||||
func TestAlluxioFileUtils_IsExist(t *testing.T) {
|
||||
|
||||
mockExec := func(p1, p2, p3 string, p4 []string) (stdout string, stderr string, e error) {
|
||||
|
||||
if strings.Contains(p4[3], NOT_EXIST) {
|
||||
return "does not exist", "", errors.New("does not exist")
|
||||
|
||||
} else if strings.Contains(p4[3], OTHER_ERR) {
|
||||
return "", "", errors.New("other error")
|
||||
} else {
|
||||
return "", "", nil
|
||||
}
|
||||
}
|
||||
|
||||
err := gohook.Hook(kubeclient.ExecCommandInContainer, mockExec, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
wrappedUnhook:=func(){
|
||||
err:=gohook.UnHook(kubeclient.ExecCommandInContainer)
|
||||
if err!=nil{
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
defer wrappedUnhook()
|
||||
|
||||
var tests = []struct {
|
||||
in string
|
||||
out bool
|
||||
noErr bool
|
||||
}{
|
||||
{NOT_EXIST, false, true},
|
||||
{OTHER_ERR, false, false},
|
||||
{FINE, true, true},
|
||||
}
|
||||
for _, test := range tests {
|
||||
found, err := AlluxioFileUtils{log: NullLogger{}}.IsExist(test.in)
|
||||
if found != test.out {
|
||||
t.Errorf("input parameter is %s,expected %t, got %t", test.in, test.out, found)
|
||||
}
|
||||
var noErr bool = (err == nil)
|
||||
if test.noErr != noErr {
|
||||
t.Errorf("input parameter is %s,expected noerr is %t", test.in, test.noErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlluxioFileUtils_Du(t *testing.T) {
|
||||
out1, out2, out3 := 111, 222, "%233"
|
||||
mockExec := func(p1, p2, p3 string, p4 []string) (stdout string, stderr string, e error) {
|
||||
|
||||
if strings.Contains(p4[4], EXEC_ERR) {
|
||||
return "does not exist", "", errors.New("exec-error")
|
||||
} else if strings.Contains(p4[4], TOO_MANY_LINES) {
|
||||
return "1\n2\n3\n4\n", "1\n2\n3\n4\n", nil
|
||||
} else if strings.Contains(p4[4], DATA_NUM) {
|
||||
return "1\n2\t3", "1\n2\t3", nil
|
||||
} else if strings.Contains(p4[4], PARSE_ERR) {
|
||||
return "1\n1\tdududu\tbbb\t", "1\n1\t2\tbbb\t", nil
|
||||
} else {
|
||||
return fmt.Sprintf("first line!\n%d\t%d\t(%s)\t2333", out1, out2, out3), "", nil
|
||||
}
|
||||
}
|
||||
|
||||
err := gohook.HookByIndirectJmp(kubeclient.ExecCommandInContainer, mockExec, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
wrappedUnhook:=func(){
|
||||
err:=gohook.UnHook(kubeclient.ExecCommandInContainer)
|
||||
if err!=nil{
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
defer wrappedUnhook()
|
||||
|
||||
var tests = []struct {
|
||||
in string
|
||||
out1, out2 int64
|
||||
out3 string
|
||||
noErr bool
|
||||
}{
|
||||
{EXEC_ERR, 0, 0, "", false},
|
||||
{TOO_MANY_LINES, 0, 0, "", false},
|
||||
{DATA_NUM, 0, 0, "", false},
|
||||
{PARSE_ERR, 0, 0, "", false},
|
||||
{FINE, int64(out1), int64(out2), out3, true},
|
||||
}
|
||||
for _, test := range tests {
|
||||
o1, o2, o3, err := AlluxioFileUtils{log: NullLogger{}}.Du(test.in)
|
||||
var noErr bool = (err == nil)
|
||||
if test.noErr != noErr {
|
||||
t.Errorf("input parameter is %s,expected noerr is %t", test.in, test.noErr)
|
||||
}
|
||||
if test.noErr {
|
||||
if o1 != test.out1 || o2 != test.out2 || o3 != test.out3 {
|
||||
t.Fatalf("input parameter is %s,output is %d,%d, %s", test.in, o1, o2, o3)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlluxioFileUtils_Count(t *testing.T) {
|
||||
out1, out2, out3 := 111, 222, 333
|
||||
mockExec := func(p1, p2, p3 string, p4 []string) (stdout string, stderr string, e error) {
|
||||
|
||||
if strings.Contains(p4[3], EXEC_ERR) {
|
||||
return "does not exist", "", errors.New("exec-error")
|
||||
} else if strings.Contains(p4[3], TOO_MANY_LINES) {
|
||||
return "1\n2\n3\n4\n", "1\n2\n3\n4\n", nil
|
||||
} else if strings.Contains(p4[3], DATA_NUM) {
|
||||
return "1\n2\t3", "1\n2\t3", nil
|
||||
} else if strings.Contains(p4[3], PARSE_ERR) {
|
||||
return "1\n1\tdududu\tbbb\t", "1\n1\t2\tbbb\t", nil
|
||||
} else {
|
||||
return fmt.Sprintf("first line!\n%d\t%d\t%d", out1, out2, out3), "", nil
|
||||
}
|
||||
}
|
||||
|
||||
err := gohook.HookByIndirectJmp(kubeclient.ExecCommandInContainer, mockExec, nil)
|
||||
wrappedUnhook:=func(){
|
||||
err:=gohook.UnHook(kubeclient.ExecCommandInContainer)
|
||||
if err!=nil{
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
defer wrappedUnhook()
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
var tests = []struct {
|
||||
in string
|
||||
out1, out2, out3 int64
|
||||
noErr bool
|
||||
}{
|
||||
{EXEC_ERR, 0, 0, 0, false},
|
||||
{TOO_MANY_LINES, 0, 0, 0, false},
|
||||
{DATA_NUM, 0, 0, 0, false},
|
||||
{PARSE_ERR, 0, 0, 0, false},
|
||||
{FINE, int64(out1), int64(out2), int64(out3), true},
|
||||
}
|
||||
for _, test := range tests {
|
||||
o1, o2, o3, err := AlluxioFileUtils{log: NullLogger{}}.Count(test.in)
|
||||
var noErr bool = (err == nil)
|
||||
if test.noErr != noErr {
|
||||
t.Errorf("input parameter is %s,expected noerr is %t", test.in, test.noErr)
|
||||
}
|
||||
if test.noErr {
|
||||
if o1 != test.out1 || o2 != test.out2 || o3 != test.out3 {
|
||||
t.Fatalf("input parameter is %s,output is %d,%d, %d", test.in, o1, o2, o3)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ func (a AlluxioFileUtils) CachedState() (cached int64, err error) {
|
|||
found := false
|
||||
stdout, stderr, err = a.exec(command, false)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("execute command %v with err: %v stdout %s and stderr %s", command, err, stdout, stderr)
|
||||
err = fmt.Errorf("execute command %v with expectedErr: %v stdout %s and stderr %s", command, err, stdout, stderr)
|
||||
return
|
||||
}
|
||||
str := strings.Split(stdout, "\n")
|
||||
|
@ -67,7 +67,7 @@ func (a AlluxioFileUtils) CleanCache(path string) (err error) {
|
|||
|
||||
stdout, stderr, err = a.exec(command, false)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("execute command %v with err: %v stdout %s and stderr %s", command, err, stdout, stderr)
|
||||
err = fmt.Errorf("execute command %v with expectedErr: %v stdout %s and stderr %s", command, err, stdout, stderr)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ func (a AlluxioFileUtils) SyncLocalDir(path string) (err error) {
|
|||
duration := time.Since(start)
|
||||
a.log.Info("du -sh", "path", path, "period", duration)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("execute command %v with err: %v stdout %s and stderr %s", command, err, stdout, stderr)
|
||||
err = fmt.Errorf("execute command %v with expectedErr: %v stdout %s and stderr %s", command, err, stdout, stderr)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ func TestSyncLocalDir(t *testing.T) {
|
|||
for _, test := range tests {
|
||||
tools := NewAlluxioFileUtils("", "", "", ctrl.Log)
|
||||
err := tools.SyncLocalDir(test.path)
|
||||
// fmt.Println(err)
|
||||
// fmt.Println(expectedErr)
|
||||
if err == nil {
|
||||
t.Errorf("expected %v, got %v", test.path, tools)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package kubeclient
|
||||
|
||||
import (
|
||||
datav1alpha1 "github.com/fluid-cloudnative/fluid/api/v1alpha1"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
"path/filepath"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest/printer"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||
"testing"
|
||||
"time"
|
||||
// +kubebuilder:scaffold:imports
|
||||
)
|
||||
|
||||
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
|
||||
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
|
||||
|
||||
var cfg *rest.Config
|
||||
var k8sClient client.Client
|
||||
var testEnv *envtest.Environment
|
||||
|
||||
func TestAPIs(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
|
||||
RunSpecsWithDefaultAndCustomReporters(t,
|
||||
"Controller Suite",
|
||||
[]Reporter{printer.NewlineReporter{}})
|
||||
}
|
||||
|
||||
var _ = BeforeSuite(func(done Done) {
|
||||
logf.SetLogger(zap.LoggerTo(GinkgoWriter, true))
|
||||
|
||||
By("bootstrapping test environment")
|
||||
testEnv = &envtest.Environment{
|
||||
CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd", "bases")},
|
||||
}
|
||||
|
||||
var err error
|
||||
cfg, err = testEnv.Start()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(cfg).ToNot(BeNil())
|
||||
|
||||
err = datav1alpha1.AddToScheme(scheme.Scheme)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// +kubebuilder:scaffold:scheme
|
||||
|
||||
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(k8sClient).ToNot(BeNil())
|
||||
|
||||
close(done)
|
||||
}, 60)
|
||||
|
||||
var _ = AfterSuite(func() {
|
||||
By("tearing down the test environment")
|
||||
err := testEnv.Stop()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
var _ = Describe("Test namespace", func() {
|
||||
Context("check if given ns exist", func() {
|
||||
It("this ns exists,and err should be nil", func() {
|
||||
err := EnsureNamespace(k8sClient, "default")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("try to get a non-existed ns,should fail", func() {
|
||||
err := EnsureNamespace(k8sClient, time.Now().String())
|
||||
Expect(err).Should(HaveOccurred())
|
||||
})
|
||||
})
|
||||
Context("test createnamespace", func() {
|
||||
namespace := "woohoo"
|
||||
It("create a new ns,err should be nil", func() {
|
||||
err := createNamespace(k8sClient, namespace)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
It("check if create ns successfully,err should nil", func() {
|
||||
err := EnsureNamespace(k8sClient, namespace)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
It("try to create ns with a conflicted name ,should fail", func() {
|
||||
err := createNamespace(k8sClient, namespace)
|
||||
Expect(err).Should(HaveOccurred())
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2019 brahma-adshonor
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,94 @@
|
|||
[![Build Status](https://kmalloc.visualstudio.com/ink/_apis/build/status/kmalloc.gohook?branchName=master)](https://kmalloc.visualstudio.com/ink/_build/latest?definitionId=1&branchName=master)
|
||||
|
||||
## Gohook
|
||||
|
||||
A funny library to hook golang function dynamically at runtime, enabling functionality like patching in dynamic language.
|
||||
|
||||
The most significant feature this library provided that makes it distinguished from others is that it supports calling back to the original function.
|
||||
|
||||
Read following blogpost for further explanation of the implementation detail: [1](https://www.cnblogs.com/catch/p/10973611.html),[2](https://onedrive.live.com/View.aspx?resid=7804A3BDAEB13A9F!58083&authkey=!AKVlLS9s9KYh07s)
|
||||
|
||||
## How it works
|
||||
|
||||
The general idea of this library is that gohook will find out the address of a go function and then insert a few jump instructions to redirect execution flow to the new function.
|
||||
|
||||
there are a few steps to perform a hook:
|
||||
|
||||
1. find out the address of a function, this can be accomplished by standard reflect library.
|
||||
2. inject jump code into target function, with carefully crafted binary instruction.
|
||||
3. implement trampoline function to enable calling back to the original function.
|
||||
|
||||
It may seem risky and dangerous to perform operations like these at first glance, but this is actually common practice in c/c++ though, you can google it, search for "hot patching" something like that for more information.
|
||||
|
||||
## Using gohook
|
||||
|
||||
5 api are exported from this library, the signatures are simple as illustrated following:
|
||||
|
||||
1. `func Hook(target, replace, trampoline interface{}) error;`
|
||||
2. `func UnHook(target interface{}) error;`
|
||||
3. `func HookByIndirectJmp(target, replace, trampoline interface{});`
|
||||
4. `func HookMethod(instance interface{}, method string, replace, trampoline interface{}) error;`
|
||||
5. `func UnHookMethod(instance interface{}, method string) error;`
|
||||
|
||||
The first 3 functions are used to hook/unhook regular functions, the rest are for instance method, as the naming implies(essentially, HookMethod(obj,x,y,z) is the same as Hook(ObjType.x,y,z)).
|
||||
|
||||
Basically, you can just call `gohook.Hook(fmt.Printf, myPrintf, myPrintfTramp)` to hook the fmt.Printf in the standard library.
|
||||
|
||||
Trampolines here serves as a shadow function after the target function is hooked, think of it as a copy of the original target function.
|
||||
|
||||
In situation where calling back to the original function is not needed, trampoline can be passed a nil value.
|
||||
|
||||
HookByIndirectJmp() differs from Hook() in that it uses rdx to perform an indirect jump from a funcval, and:
|
||||
|
||||
1. `rdx is the context register used by compiler to access funcval.`
|
||||
2. `funcval contains extra information for a closure, which is used by compiler and runtime.`
|
||||
|
||||
this makes it possible to hook closure function and function created by reflect.MakeFunc(), **in a less compatible way**, since the implementaion of this hook has to guess the memory layout of a reflect.Value object, which may vary from different version of runtime.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"github.com/kmalloc/gohook"
|
||||
)
|
||||
|
||||
func myPrintln(a ...interface{}) (n int, err error) {
|
||||
fmt.Fprintln(os.Stdout, "before real Printfln")
|
||||
return myPrintlnTramp(a...)
|
||||
}
|
||||
|
||||
func myPrintlnTramp(a ...interface{}) (n int, err error) {
|
||||
// a dummy function to make room for a shadow copy of the original function.
|
||||
// it doesn't matter what we do here, just to create an addressable function with adequate size.
|
||||
myPrintlnTramp(a...)
|
||||
myPrintlnTramp(a...)
|
||||
myPrintlnTramp(a...)
|
||||
|
||||
for {
|
||||
fmt.Printf("hello")
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
gohook.Hook(fmt.Println, myPrintln, myPrintlnTramp)
|
||||
fmt.Println("hello world!")
|
||||
}
|
||||
```
|
||||
|
||||
For more usage example, please refer to the example folder.
|
||||
|
||||
## Notes
|
||||
|
||||
1. 32 bit mode may not work, far jump is not handled.
|
||||
2. trampoline is used to make room for the original function, it will be overwrited.
|
||||
3. in case of small function which may be inlined, gohook may fail:
|
||||
- disable inlining by passig -gcflags=-l to build cmd.
|
||||
4. this library is created for integrated testing, and not fully tested in production(yet), user discretion is advised.
|
||||
5. escape analysis may be influenced:
|
||||
- deep copy arguments if you need to copy argument from replacement function(see func_stack_test.go).
|
||||
- escape those arguments from trampoline(by passing it to a goroutine or to other function that can escape it)
|
||||
if that argument is allocated from the replacement function.
|
|
@ -0,0 +1,861 @@
|
|||
package gohook
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/arch/x86/x86asm"
|
||||
)
|
||||
|
||||
type CodeFix struct {
|
||||
Code []byte
|
||||
Addr uintptr
|
||||
Foreign bool
|
||||
}
|
||||
|
||||
var (
|
||||
minJmpCodeSize = 0
|
||||
elfInfo, _ = NewElfInfo()
|
||||
|
||||
errInplaceFixSizeNotEnough = fmt.Errorf("func size exceed during inplace fix")
|
||||
|
||||
funcPrologue32 = defaultFuncPrologue32
|
||||
funcPrologue64 = defaultFuncPrologue64
|
||||
|
||||
// ======================condition jump instruction========================
|
||||
// JA JAE JB JBE JCXZ JE JECXZ JG JGE JL JLE JMP JNE JNO JNP JNS JO JP JRCXZ JS
|
||||
|
||||
// one byte opcode, one byte relative offset
|
||||
twoByteCondJmp = []byte{0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0xe3}
|
||||
// two byte opcode, four byte relative offset
|
||||
sixByteCondJmp = []uint16{0x0f80, 0x0f81, 0x0f82, 0x0f83, 0x0f84, 0x0f85, 0x0f86, 0x0f87, 0x0f88, 0x0f89, 0x0f8a, 0x0f8b, 0x0f8c, 0x0f8d, 0x0f8e, 0x0f8f}
|
||||
|
||||
// ====================== jump instruction========================
|
||||
// one byte opcode, one byte relative offset
|
||||
twoByteJmp = []byte{0xeb}
|
||||
// one byte opcode, four byte relative offset
|
||||
fiveByteJmp = []byte{0xe9}
|
||||
|
||||
// ====================== call instruction========================
|
||||
// one byte opcode, 4 byte relative offset
|
||||
fiveByteCall = []byte{0xe8}
|
||||
|
||||
// ====================== ret instruction========================
|
||||
// return instruction, no operand
|
||||
oneByteRet = []byte{0xc3, 0xcb}
|
||||
// return instruction, one byte opcode, 2 byte operand
|
||||
threeByteRet = []byte{0xc2, 0xca}
|
||||
)
|
||||
|
||||
const (
|
||||
FT_CondJmp = 1
|
||||
FT_JMP = 2
|
||||
FT_CALL = 3
|
||||
FT_RET = 4
|
||||
FT_OTHER = 5
|
||||
FT_INVALID = 6
|
||||
FT_SKIP = 7
|
||||
FT_OVERFLOW = 8
|
||||
)
|
||||
|
||||
func SetMinJmpCodeSize(sz int) {
|
||||
minJmpCodeSize = sz
|
||||
}
|
||||
|
||||
func ResetFuncPrologue() {
|
||||
funcPrologue32 = defaultFuncPrologue32
|
||||
funcPrologue64 = defaultFuncPrologue64
|
||||
}
|
||||
|
||||
func SetFuncPrologue(mode int, data []byte) {
|
||||
if mode == 32 {
|
||||
funcPrologue32 = make([]byte, len(data))
|
||||
copy(funcPrologue32, data)
|
||||
} else {
|
||||
funcPrologue64 = make([]byte, len(data))
|
||||
copy(funcPrologue64, data)
|
||||
}
|
||||
}
|
||||
|
||||
func GetInsLenGreaterThan(mode int, data []byte, least int) int {
|
||||
if len(data) < least {
|
||||
return 0
|
||||
}
|
||||
|
||||
curLen := 0
|
||||
d := data[curLen:]
|
||||
for {
|
||||
if len(d) <= 0 {
|
||||
break
|
||||
}
|
||||
|
||||
if curLen >= least {
|
||||
break
|
||||
}
|
||||
|
||||
inst, err := x86asm.Decode(d, mode)
|
||||
if err != nil || (inst.Opcode == 0 && inst.Len == 1 && inst.Prefix[0] == x86asm.Prefix(d[0])) {
|
||||
break
|
||||
}
|
||||
|
||||
if inst.Len == 1 && d[0] == 0xcc {
|
||||
// 0xcc -> int3, trap to debugger, padding to function end
|
||||
break
|
||||
}
|
||||
|
||||
curLen = curLen + inst.Len
|
||||
d = data[curLen:]
|
||||
}
|
||||
|
||||
return curLen
|
||||
}
|
||||
|
||||
func isByteOverflow(v int32) bool {
|
||||
if v > 0 {
|
||||
if v > math.MaxInt8 {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if v < math.MinInt8 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func isIntOverflow(v int64) bool {
|
||||
if v > 0 {
|
||||
if v > math.MaxInt32 {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if v < math.MinInt32 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func calcOffset(insSz int, startAddr, curAddr, to uintptr, to_sz int, offset int32) int64 {
|
||||
newAddr := curAddr
|
||||
absAddr := curAddr + uintptr(insSz) + uintptr(offset)
|
||||
|
||||
if curAddr < startAddr+uintptr(to_sz) {
|
||||
newAddr = to + (curAddr - startAddr)
|
||||
}
|
||||
|
||||
if absAddr >= startAddr && absAddr < startAddr+uintptr(to_sz) {
|
||||
absAddr = to + (absAddr - startAddr)
|
||||
}
|
||||
|
||||
return int64(uint64(absAddr) - uint64(newAddr) - uint64(insSz))
|
||||
}
|
||||
|
||||
func translateJump(off int64, code []byte) ([]byte, error) {
|
||||
if code[0] == 0xe3 {
|
||||
return nil, errors.New("not supported JCXZ instruction(0xe3)")
|
||||
}
|
||||
|
||||
if code[0] == 0xeb {
|
||||
ret := make([]byte, 5)
|
||||
ret[0] = 0xe9
|
||||
|
||||
ret[1] = byte(off)
|
||||
ret[2] = byte(off >> 8)
|
||||
ret[3] = byte(off >> 16)
|
||||
ret[4] = byte(off >> 24)
|
||||
return ret, nil
|
||||
} else if code[0] >= 0x70 && code[0] <= 0x7f {
|
||||
ret := make([]byte, 6)
|
||||
ret[0] = 0x0f
|
||||
ret[1] = 0x80 + code[0] - 0x70
|
||||
|
||||
ret[2] = byte(off)
|
||||
ret[3] = byte(off >> 8)
|
||||
ret[4] = byte(off >> 16)
|
||||
ret[5] = byte(off >> 24)
|
||||
return ret, nil
|
||||
} else {
|
||||
return nil, errors.New("cannot fix unsupported jump instruction inplace")
|
||||
}
|
||||
}
|
||||
|
||||
func adjustInstructionOffset(code []byte, off int64) ([]byte, error) {
|
||||
if code[0] == 0xe3 || code[0] == 0xeb || (code[0] >= 0x70 && code[0] <= 0x7f) {
|
||||
offset := int(int8(code[1]))
|
||||
if offset == int(off) {
|
||||
return code, nil
|
||||
}
|
||||
if isByteOverflow(int32(off)) {
|
||||
return nil, fmt.Errorf("byte overflow in adjusting offset")
|
||||
}
|
||||
code[1] = byte(int8(off))
|
||||
} else if code[0] == 0x0f && (code[1] >= 0x80 && code[1] <= 0x8f) {
|
||||
offset := int(int32(uint32(code[2]) | (uint32(code[3]) << 8) | (uint32(code[4]) << 16) | (uint32(code[5]) << 24)))
|
||||
if offset == int(off) {
|
||||
return code, nil
|
||||
}
|
||||
if isIntOverflow(off) {
|
||||
return nil, fmt.Errorf("int overflow in adjusting 6-bytes inst offset")
|
||||
}
|
||||
code[2] = byte(off)
|
||||
code[3] = byte(off >> 8)
|
||||
code[4] = byte(off >> 16)
|
||||
code[5] = byte(off >> 24)
|
||||
} else if code[0] == 0xe9 || code[0] == 0xe8 {
|
||||
offset := int(int32(uint32(code[1]) | (uint32(code[2]) << 8) | (uint32(code[3]) << 16) | (uint32(code[4]) << 24)))
|
||||
if offset == int(off) {
|
||||
return code, nil
|
||||
}
|
||||
if isIntOverflow(off) {
|
||||
return nil, fmt.Errorf("int overflow in adjusting 5-bytes inst offset")
|
||||
}
|
||||
code[1] = byte(off)
|
||||
code[2] = byte(off >> 8)
|
||||
code[3] = byte(off >> 16)
|
||||
code[4] = byte(off >> 24)
|
||||
} else if code[0] == 0x48 && (code[1] == 0x8b || code[1] == 0x8d) && (code[2]&0x05) == 0x05 { // mod == 00 r/m == 101
|
||||
// rip relative addressing: mov/lea
|
||||
// intel software development manual: `Addressing-Mode Encoding of ModR/M and SIB Bytes` && `RIP-Relative Addressing`
|
||||
// or https://www.cs.uaf.edu/2016/fall/cs301/lecture/09_28_machinecode.html
|
||||
offset := int(int32(uint32(code[3]) | (uint32(code[4]) << 8) | (uint32(code[5]) << 16) | (uint32(code[6]) << 24)))
|
||||
if offset == int(off) {
|
||||
return code, nil
|
||||
}
|
||||
code[3] = byte(off)
|
||||
code[4] = byte(off >> 8)
|
||||
code[5] = byte(off >> 16)
|
||||
code[6] = byte(off >> 24)
|
||||
} else {
|
||||
return nil, fmt.Errorf("not jump instruction")
|
||||
}
|
||||
|
||||
return code, nil
|
||||
}
|
||||
|
||||
func calcJumpToAbsAddr(mode int, addr uintptr, code []byte) uintptr {
|
||||
sz := 0
|
||||
offset := 0
|
||||
|
||||
if code[0] == 0xe3 || code[0] == 0xeb || (code[0] >= 0x70 && code[0] <= 0x7f) {
|
||||
sz = 2
|
||||
offset = int(int8(code[1]))
|
||||
}
|
||||
|
||||
if code[0] == 0x0f && (code[1] >= 0x80 && code[1] <= 0x8f) {
|
||||
sz = 6
|
||||
offset = int(int32(uint32(code[2]) | (uint32(code[3]) << 8) | (uint32(code[4]) << 16) | (uint32(code[5]) << 24)))
|
||||
}
|
||||
|
||||
if code[0] == 0xe9 || code[0] == 0xe8 {
|
||||
sz = 5
|
||||
offset = int(int32(uint32(code[1]) | (uint32(code[2]) << 8) | (uint32(code[3]) << 16) | (uint32(code[4]) << 24)))
|
||||
}
|
||||
|
||||
if code[0] == 0x48 && (code[1] == 0x8b || code[1] == 0x8d) && (code[2]&0x05) == 0x05 { // mod == 00 r/m == 101
|
||||
// rip relative addressing: mov/lea
|
||||
// intel software development manual: `Addressing-Mode Encoding of ModR/M and SIB Bytes` && `RIP-Relative Addressing`
|
||||
// or https://www.cs.uaf.edu/2016/fall/cs301/lecture/09_28_machinecode.html
|
||||
sz = 7
|
||||
offset = int(int32(uint32(code[3]) | (uint32(code[4]) << 8) | (uint32(code[5]) << 16) | (uint32(code[6]) << 24)))
|
||||
}
|
||||
|
||||
if sz == 0 {
|
||||
return uintptr(0)
|
||||
}
|
||||
|
||||
return addr + uintptr(sz) + uintptr(offset)
|
||||
}
|
||||
|
||||
func FixOneInstruction(mode int, fix_recursive_call bool, startAddr, curAddr uintptr, code []byte, to uintptr, to_sz int) (int, int, []byte) {
|
||||
nc := make([]byte, len(code))
|
||||
copy(nc, code)
|
||||
|
||||
if code[0] == 0xe3 || code[0] == 0xeb || (code[0] >= 0x70 && code[0] <= 0x7f) {
|
||||
// two byte condition jump, two byte jmp
|
||||
nc = nc[:2]
|
||||
off := calcOffset(2, startAddr, curAddr, to, to_sz, int32(int8(code[1])))
|
||||
if off != int64(int8(nc[1])) {
|
||||
if isByteOverflow(int32(off)) {
|
||||
// overfloat, cannot fix this with one byte operand
|
||||
return 2, FT_OVERFLOW, nc
|
||||
}
|
||||
nc[1] = byte(off)
|
||||
return 2, FT_CondJmp, nc
|
||||
}
|
||||
return 2, FT_SKIP, nc
|
||||
}
|
||||
|
||||
if code[0] == 0x0f && (code[1] >= 0x80 && code[1] <= 0x8f) {
|
||||
// six byte condition jump
|
||||
nc = nc[:6]
|
||||
off1 := (uint32(code[2]) | (uint32(code[3]) << 8) | (uint32(code[4]) << 16) | (uint32(code[5]) << 24))
|
||||
off2 := uint64(calcOffset(6, startAddr, curAddr, to, to_sz, int32(off1)))
|
||||
if uint64(int32(off1)) != off2 {
|
||||
if isIntOverflow(int64(off2)) {
|
||||
// overfloat, cannot fix this with four byte operand
|
||||
return 6, FT_OVERFLOW, nc
|
||||
}
|
||||
nc[2] = byte(off2)
|
||||
nc[3] = byte(off2 >> 8)
|
||||
nc[4] = byte(off2 >> 16)
|
||||
nc[5] = byte(off2 >> 24)
|
||||
return 6, FT_CondJmp, nc
|
||||
}
|
||||
return 6, FT_SKIP, nc
|
||||
}
|
||||
|
||||
if code[0] == 0xe9 || code[0] == 0xe8 {
|
||||
// five byte jmp, five byte call
|
||||
nc = nc[:5]
|
||||
off1 := (uint32(code[1]) | (uint32(code[2]) << 8) | (uint32(code[3]) << 16) | (uint32(code[4]) << 24))
|
||||
|
||||
off2 := uint64(0)
|
||||
if !fix_recursive_call && code[0] == 0xe8 && startAddr == (curAddr+uintptr(5)+uintptr(int32(off1))) {
|
||||
// don't fix recursive call
|
||||
off2 = uint64(int32(off1))
|
||||
} else {
|
||||
off2 = uint64(calcOffset(5, startAddr, curAddr, to, to_sz, int32(off1)))
|
||||
}
|
||||
|
||||
if uint64(int32(off1)) != off2 {
|
||||
if isIntOverflow(int64(off2)) {
|
||||
// overfloat, cannot fix this with four byte operand
|
||||
return 5, FT_OVERFLOW, nc
|
||||
}
|
||||
nc[1] = byte(off2)
|
||||
nc[2] = byte(off2 >> 8)
|
||||
nc[3] = byte(off2 >> 16)
|
||||
nc[4] = byte(off2 >> 24)
|
||||
return 5, FT_JMP, nc
|
||||
}
|
||||
return 5, FT_SKIP, nc
|
||||
}
|
||||
|
||||
// ret instruction just return, no fix is needed.
|
||||
if code[0] == 0xc3 || code[0] == 0xcb {
|
||||
// one byte ret
|
||||
nc = nc[:1]
|
||||
return 1, FT_RET, nc
|
||||
}
|
||||
|
||||
if code[0] == 0xc2 || code[0] == 0xca {
|
||||
// three byte ret
|
||||
nc = nc[:3]
|
||||
return 3, FT_RET, nc
|
||||
}
|
||||
|
||||
inst, err := x86asm.Decode(code, mode)
|
||||
if err != nil || (inst.Opcode == 0 && inst.Len == 1 && inst.Prefix[0] == x86asm.Prefix(code[0])) {
|
||||
return 0, FT_INVALID, nc
|
||||
}
|
||||
|
||||
if inst.Len == 1 && code[0] == 0xcc {
|
||||
return 0, FT_INVALID, nc
|
||||
}
|
||||
|
||||
sz := inst.Len
|
||||
nc = nc[:sz]
|
||||
return sz, FT_OTHER, nc
|
||||
}
|
||||
|
||||
func doFixTargetFuncCode(all bool, mode int, start uintptr, funcSz int, to uintptr, move_sz int, inst []CodeFix) ([]CodeFix, error) {
|
||||
fix := make([]CodeFix, 0, 64)
|
||||
|
||||
curSz := 0
|
||||
curAddr := start
|
||||
|
||||
i := 0
|
||||
for i = 0; i < len(inst); i++ {
|
||||
if curSz >= move_sz {
|
||||
break
|
||||
}
|
||||
|
||||
code := inst[i].Code
|
||||
sz, ft, nc := FixOneInstruction(mode, false, start, curAddr, code, to, move_sz)
|
||||
|
||||
if sz == 0 && ft == FT_INVALID {
|
||||
// the end or unrecognized instruction
|
||||
return nil, errors.New(fmt.Sprintf("invalid instruction scanned, addr:0x%x", curAddr))
|
||||
} else if sz == 5 && nc[0] == 0xe8 {
|
||||
// call instruction is not allowed to move.
|
||||
// this will mess up with golang stack reallocation.
|
||||
return nil, fmt.Errorf("call instruction is not allowed to move")
|
||||
}
|
||||
|
||||
if ft == FT_RET {
|
||||
return nil, errors.New(fmt.Sprintf("ret instruction in patching erea is not allowed, addr:0x%x", curAddr))
|
||||
}
|
||||
|
||||
if ft == FT_OVERFLOW {
|
||||
return nil, errors.New(fmt.Sprintf("jmp instruction in patching erea overflow, addr:0x%x", curAddr))
|
||||
}
|
||||
|
||||
if ft != FT_OTHER && ft != FT_SKIP {
|
||||
fix = append(fix, CodeFix{Code: nc, Addr: curAddr, Foreign: true})
|
||||
} else if all {
|
||||
fix = append(fix, CodeFix{Code: nc, Addr: curAddr, Foreign: true})
|
||||
}
|
||||
|
||||
curSz += sz
|
||||
curAddr = start + uintptr(curSz)
|
||||
}
|
||||
|
||||
for ; i < len(inst); i++ {
|
||||
if funcSz > 0 && int(curAddr-start) >= funcSz {
|
||||
break
|
||||
}
|
||||
|
||||
code := inst[i].Code
|
||||
sz, ft, nc := FixOneInstruction(mode, false, start, curAddr, code, to, move_sz)
|
||||
|
||||
if sz == 0 && ft == FT_INVALID {
|
||||
// the end or unrecognized instruction
|
||||
break
|
||||
}
|
||||
|
||||
if ft == FT_OVERFLOW {
|
||||
return nil, errors.New(fmt.Sprintf("jmp instruction in body overflow, addr:0x%x", curAddr))
|
||||
}
|
||||
|
||||
if ft != FT_OTHER && ft != FT_RET && ft != FT_SKIP {
|
||||
fix = append(fix, CodeFix{Code: nc, Addr: curAddr, Foreign: false})
|
||||
} else if all {
|
||||
fix = append(fix, CodeFix{Code: nc, Addr: curAddr, Foreign: false})
|
||||
}
|
||||
|
||||
curSz += sz
|
||||
curAddr = start + uintptr(curSz)
|
||||
}
|
||||
|
||||
return fix, nil
|
||||
}
|
||||
|
||||
// FixTargetFuncCode fix function code starting at address [start]
|
||||
// parameter 'funcSz' may not specify, in which case, we need to find out the end by scanning next prologue or finding invalid instruction.
|
||||
// 'to' specifys a new location, to which 'move_sz' bytes instruction will be copied
|
||||
// since move_sz byte instructions will be copied, those relative jump instruction need to be fixed.
|
||||
func FixTargetFuncCode(mode int, start uintptr, funcSz uint32, to uintptr, move_sz int) ([]CodeFix, error) {
|
||||
inst, _ := parseInstruction(mode, start, int(funcSz), false)
|
||||
return doFixTargetFuncCode(false, mode, start, int(funcSz), to, move_sz, inst)
|
||||
}
|
||||
|
||||
func GetFuncSizeByGuess(mode int, start uintptr, minimal bool) (uint32, error) {
|
||||
funcPrologue := funcPrologue64
|
||||
if mode == 32 {
|
||||
funcPrologue = funcPrologue32
|
||||
}
|
||||
|
||||
prologueLen := len(funcPrologue)
|
||||
code := makeSliceFromPointer(start, 16) // instruction takes at most 16 bytes
|
||||
|
||||
/* prologue is not required
|
||||
if !bytes.Equal(funcPrologue, code[:prologueLen]) { // not valid function start or invalid prologue
|
||||
return 0, errors.New(fmt.Sprintf("no func prologue, addr:0x%x", start))
|
||||
}
|
||||
*/
|
||||
|
||||
int3_found := false
|
||||
curLen := uint32(0)
|
||||
|
||||
for {
|
||||
inst, err := x86asm.Decode(code, mode)
|
||||
if err != nil || (inst.Opcode == 0 && inst.Len == 1 && inst.Prefix[0] == x86asm.Prefix(code[0])) {
|
||||
break
|
||||
}
|
||||
|
||||
if inst.Len == 1 && code[0] == 0xcc {
|
||||
// 0xcc -> int3, trap to debugger, padding to function end
|
||||
if minimal {
|
||||
break
|
||||
}
|
||||
int3_found = true
|
||||
} else if int3_found {
|
||||
break
|
||||
}
|
||||
|
||||
curLen = curLen + uint32(inst.Len)
|
||||
code = makeSliceFromPointer(start+uintptr(curLen), 16) // instruction takes at most 16 bytes
|
||||
|
||||
if bytes.Equal(funcPrologue, code[:prologueLen]) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return curLen, nil
|
||||
}
|
||||
|
||||
// sz size of source function
|
||||
// WARNING: copy function won't work in copystack(since go 1.3).
|
||||
// runtime will copy stack to new area and fix those weird stuff(pointer/rbp etc), this will crash trampoline function.
|
||||
// since copying function makes trampoline a completely different function, with completely different stack layout which is
|
||||
// not known to runtime.
|
||||
// solution to this is, we should just copy those non-call instructions to trampoline. in this way we don't mess up with runtime.
|
||||
// TODO/FIXME
|
||||
func copyFuncInstruction(mode int, from, to uintptr, sz int, allowCall bool) ([]CodeFix, error) {
|
||||
curSz := 0
|
||||
curAddr := from
|
||||
fix := make([]CodeFix, 0, 256)
|
||||
|
||||
for {
|
||||
if curSz >= sz {
|
||||
break
|
||||
}
|
||||
|
||||
code := makeSliceFromPointer(curAddr, 16) // instruction takes at most 16 bytes
|
||||
sz, ft, nc := FixOneInstruction(mode, true, from, curAddr, code, to, sz)
|
||||
|
||||
if sz == 0 && ft == FT_INVALID {
|
||||
// the end or unrecognized instruction
|
||||
break
|
||||
} else if !allowCall && sz == 5 && nc[0] == 0xe8 {
|
||||
// call instruction is not allowed to move.
|
||||
// this will mess up with golang stack reallocation.
|
||||
return nil, fmt.Errorf("call instruction is not allowed to copy")
|
||||
}
|
||||
|
||||
if ft == FT_OVERFLOW {
|
||||
return nil, fmt.Errorf("overflow instruction in copying function, addr:0x%x", curAddr)
|
||||
}
|
||||
|
||||
to_addr := (to + (curAddr - from))
|
||||
fix = append(fix, CodeFix{Code: nc, Addr: to_addr})
|
||||
|
||||
curSz += sz
|
||||
curAddr = from + uintptr(curSz)
|
||||
}
|
||||
|
||||
to_addr := (to + (curAddr - from))
|
||||
fix = append(fix, CodeFix{Code: []byte{0xcc}, Addr: to_addr})
|
||||
return fix, nil
|
||||
}
|
||||
|
||||
func adjustJmpOffset(mode int, start, delem uintptr, funcSize, moveSize int, inst []CodeFix) error {
|
||||
funcEnd := start + uintptr(funcSize)
|
||||
for i := range inst {
|
||||
code := inst[i].Code
|
||||
curAddr := inst[i].Addr
|
||||
absAddr := calcJumpToAbsAddr(mode, curAddr, code)
|
||||
|
||||
if curAddr > delem && curAddr < funcEnd {
|
||||
inst[i].Addr = curAddr + uintptr(moveSize)
|
||||
}
|
||||
|
||||
if absAddr != uintptr(0) {
|
||||
delta := absAddr - curAddr - uintptr(len(code))
|
||||
off := int64(delta)
|
||||
if unsafe.Sizeof(uintptr(0)) == unsafe.Sizeof(int32(0)) {
|
||||
off = int64(int32(delta))
|
||||
}
|
||||
|
||||
// fmt.Printf("adjust inst at:%x, sz:%d, delem:%x, target:%x, funcEnd:%x, off:%x\n", curAddr, len(code), delem, absAddr, funcEnd, uintptr(off))
|
||||
|
||||
if (curAddr < delem || curAddr >= funcEnd) && absAddr > delem && absAddr < funcEnd {
|
||||
off += int64(moveSize)
|
||||
} else if (curAddr >= delem && curAddr < funcEnd) && (absAddr <= delem || absAddr >= funcEnd) {
|
||||
off -= int64(moveSize)
|
||||
} else {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
c, err := adjustInstructionOffset(code, off)
|
||||
if err != nil {
|
||||
return fmt.Errorf("err occurs adjusting inst, addr:%x,off:%x,err:%s", curAddr, off, err.Error())
|
||||
}
|
||||
|
||||
inst[i].Code = c
|
||||
// absAddr = calcJumpToAbsAddr(mode, inst[i].Addr, code)
|
||||
// fmt.Printf("after adjust inst, old addr:%x, new addr:%x, target:%x\n", curAddr, inst[i].Addr, absAddr)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func translateShortJump(mode int, addr, to uintptr, inst []CodeFix, funcSz, move_sz, jumpSize int) (int, []CodeFix, error) {
|
||||
newSz := 0
|
||||
fix := make([]CodeFix, 0, 256)
|
||||
|
||||
for i := range inst {
|
||||
code := inst[i].Code
|
||||
curAddr := inst[i].Addr
|
||||
sz, ft, _ := FixOneInstruction(mode, false, addr, curAddr, code, to, move_sz)
|
||||
|
||||
if sz == 0 && ft == FT_INVALID {
|
||||
// the end or unrecognized instruction
|
||||
break
|
||||
}
|
||||
|
||||
foreign := false
|
||||
if curAddr < addr+uintptr(move_sz) {
|
||||
foreign = true
|
||||
}
|
||||
|
||||
if ft == FT_OVERFLOW {
|
||||
if sz != 2 {
|
||||
return 0, nil, fmt.Errorf("inst overflow with size != 2")
|
||||
}
|
||||
|
||||
nc, err := translateJump(int64(int8(code[1])), code)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
delta := len(nc) - len(code)
|
||||
if curAddr < addr+uintptr(move_sz) {
|
||||
move_sz += delta
|
||||
}
|
||||
|
||||
inst[i].Code = nc
|
||||
// fmt.Printf("extent overflow inst at:%x, sz:%d, move sz:%d\n", curAddr, len(nc), move_sz)
|
||||
|
||||
err = adjustJmpOffset(mode, addr, curAddr, funcSz, delta, inst[i:])
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
err = adjustJmpOffset(mode, addr, curAddr, funcSz, delta, fix)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
newSz += len(inst[i].Code)
|
||||
fix = append(fix, CodeFix{Code: inst[i].Code, Addr: inst[i].Addr, Foreign: foreign})
|
||||
}
|
||||
|
||||
if newSz-move_sz > funcSz-jumpSize {
|
||||
return move_sz, fix, errInplaceFixSizeNotEnough
|
||||
}
|
||||
|
||||
return move_sz, fix, nil
|
||||
}
|
||||
|
||||
func parseInstruction(mode int, addr uintptr, funcSz int, minimal bool) ([]CodeFix, error) {
|
||||
funcPrologue := funcPrologue64
|
||||
if mode == 32 {
|
||||
funcPrologue = funcPrologue32
|
||||
}
|
||||
|
||||
prologueLen := len(funcPrologue)
|
||||
code := makeSliceFromPointer(addr, 16) // instruction takes at most 16 bytes
|
||||
|
||||
curLen := 0
|
||||
int3_found := false
|
||||
|
||||
ret := make([]CodeFix, 0, 258)
|
||||
|
||||
for {
|
||||
if funcSz > 0 && curLen >= funcSz {
|
||||
break
|
||||
}
|
||||
|
||||
inst, err := x86asm.Decode(code, mode)
|
||||
if err != nil || (inst.Opcode == 0 && inst.Len == 1 && inst.Prefix[0] == x86asm.Prefix(code[0])) {
|
||||
break
|
||||
}
|
||||
|
||||
if inst.Len == 1 && code[0] == 0xcc {
|
||||
// 0xcc -> int3, trap to debugger, padding to function end
|
||||
if minimal {
|
||||
break
|
||||
}
|
||||
int3_found = true
|
||||
} else if int3_found {
|
||||
break
|
||||
}
|
||||
|
||||
c := make([]byte, inst.Len)
|
||||
copy(c, code)
|
||||
cf := CodeFix{Addr: addr + uintptr(curLen), Code: c}
|
||||
ret = append(ret, cf)
|
||||
|
||||
curLen = curLen + inst.Len
|
||||
code = makeSliceFromPointer(addr+uintptr(curLen), 16)
|
||||
|
||||
if bytes.Equal(funcPrologue, code[:prologueLen]) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func fixFuncInstructionInplace(mode int, addr, to uintptr, funcSz int, move_sz int, jumpSize int) ([]CodeFix, error) {
|
||||
/*
|
||||
trail := makeSliceFromPointer(addr+uintptr(funcSz), 1024)
|
||||
for i := 0; i < len(trail); i++ {
|
||||
if trail[i] != 0xcc {
|
||||
break
|
||||
}
|
||||
funcSz++
|
||||
}
|
||||
*/
|
||||
|
||||
code, _ := parseInstruction(mode, addr, funcSz, false)
|
||||
move_sz, fix, err := translateShortJump(mode, addr, to, code, funcSz, move_sz, jumpSize)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fix, err1 := doFixTargetFuncCode(true, mode, addr, funcSz, to, move_sz, fix)
|
||||
|
||||
if err1 != nil {
|
||||
return fix, err1
|
||||
}
|
||||
|
||||
curAddr := to
|
||||
firstBody := addr
|
||||
for i := range fix {
|
||||
if !fix[i].Foreign {
|
||||
firstBody = fix[i].Addr
|
||||
break
|
||||
}
|
||||
|
||||
// fmt.Printf("foreign addr:%x, sz:%d\n", curAddr, len(fix[i].Code))
|
||||
|
||||
fix[i].Addr = curAddr
|
||||
curAddr += uintptr(len(fix[i].Code))
|
||||
}
|
||||
|
||||
mvAddr := addr + uintptr(jumpSize)
|
||||
msz := -int(firstBody - mvAddr)
|
||||
|
||||
if msz != 0 {
|
||||
// fmt.Printf("now move to the front, msz:%d\n", msz)
|
||||
err2 := adjustJmpOffset(mode, addr, mvAddr, funcSz, msz, fix)
|
||||
if err2 != nil {
|
||||
// fmt.Printf("error in fixing inplace\n")
|
||||
return nil, err2
|
||||
}
|
||||
}
|
||||
|
||||
// fmt.Printf("done fixing inplace\n")
|
||||
return fix, nil
|
||||
}
|
||||
|
||||
func genJumpCode(mode int, rdxIndirect bool, to, from uintptr) []byte {
|
||||
// 1. use relaive jump if |from-to| < 2G
|
||||
// 2. otherwise, push target, then ret
|
||||
|
||||
var code []byte
|
||||
|
||||
if rdxIndirect {
|
||||
// rdx indirect jump.
|
||||
// 'to' :data pointer from reflect.Value, pointed to a funcValue, and the first field of funcval is a pointer to the real func.
|
||||
// 'from': this is the instruction code addr of the target function.
|
||||
|
||||
// by convention, rdx is the context register pointed to a funcval.
|
||||
// funcval of a closure function contains extra information used by compiler and runtime.
|
||||
// so using indirect jmp by rdx makes it possible to hook closure func and func created by reflect.MakeFunc
|
||||
|
||||
// caution: 'to' funcval must stay alive after hook is installed.
|
||||
if mode == 32 {
|
||||
code = []byte{
|
||||
0xBA,
|
||||
byte(to),
|
||||
byte(to >> 8),
|
||||
byte(to >> 16),
|
||||
byte(to >> 24), // mov edx,to
|
||||
0xFF, 0x22, // jmp DWORD PTR [edx]
|
||||
}
|
||||
} else {
|
||||
code = []byte{
|
||||
0x48, 0xBA,
|
||||
byte(to),
|
||||
byte(to >> 8),
|
||||
byte(to >> 16),
|
||||
byte(to >> 24),
|
||||
byte(to >> 32),
|
||||
byte(to >> 40),
|
||||
byte(to >> 48),
|
||||
byte(to >> 56), // movabs rdx,to
|
||||
0xFF, 0x22, // jmp QWORD PTR [rdx]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
delta := int64(from - to)
|
||||
if unsafe.Sizeof(uintptr(0)) == unsafe.Sizeof(int32(0)) {
|
||||
delta = int64(int32(from - to))
|
||||
}
|
||||
|
||||
relative := (delta <= 0x7fffffff)
|
||||
|
||||
if delta < 0 {
|
||||
delta = -delta
|
||||
relative = (delta <= 0x80000000)
|
||||
}
|
||||
|
||||
// relative = false
|
||||
|
||||
if relative {
|
||||
var dis uint32
|
||||
if to > from {
|
||||
dis = uint32(int32(to-from) - 5)
|
||||
} else {
|
||||
dis = uint32(-int32(from-to) - 5)
|
||||
}
|
||||
code = []byte{
|
||||
0xe9,
|
||||
byte(dis),
|
||||
byte(dis >> 8),
|
||||
byte(dis >> 16),
|
||||
byte(dis >> 24),
|
||||
}
|
||||
} else if mode == 32 {
|
||||
code = []byte{
|
||||
0x68, // push
|
||||
byte(to),
|
||||
byte(to >> 8),
|
||||
byte(to >> 16),
|
||||
byte(to >> 24),
|
||||
0xc3, // retn
|
||||
}
|
||||
} else if mode == 64 {
|
||||
// push does not operate on 64bit imm, workarounds are:
|
||||
// 1. move to register(eg, %rdx), then push %rdx, however, overwriting register may cause problem if not handled carefully.
|
||||
// 2. push twice, preferred.
|
||||
/*
|
||||
code = []byte{
|
||||
0x48, // prefix
|
||||
0xba, // mov to %rdx
|
||||
byte(to), byte(to >> 8), byte(to >> 16), byte(to >> 24),
|
||||
byte(to >> 32), byte(to >> 40), byte(to >> 48), byte(to >> 56),
|
||||
0x52, // push %rdx
|
||||
0xc3, // retn
|
||||
}
|
||||
*/
|
||||
code = []byte{
|
||||
0x68, //push
|
||||
byte(to), byte(to >> 8), byte(to >> 16), byte(to >> 24),
|
||||
0xc7, 0x44, 0x24, // mov $value, 4%rsp
|
||||
0x04, // rsp + 4
|
||||
byte(to >> 32), byte(to >> 40), byte(to >> 48), byte(to >> 56),
|
||||
0xc3, // retn
|
||||
}
|
||||
} else {
|
||||
panic("invalid mode")
|
||||
}
|
||||
}
|
||||
|
||||
sz := len(code)
|
||||
if minJmpCodeSize > 0 && sz < minJmpCodeSize {
|
||||
nop := make([]byte, 0, minJmpCodeSize-sz)
|
||||
for {
|
||||
if len(nop) >= minJmpCodeSize-sz {
|
||||
break
|
||||
}
|
||||
nop = append(nop, 0x90)
|
||||
}
|
||||
|
||||
code = append(code, nop...)
|
||||
}
|
||||
|
||||
return code
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
# Go
|
||||
# Build your Go project.
|
||||
# Add steps that test, save build artifacts, deploy, and more:
|
||||
# https://docs.microsoft.com/azure/devops/pipelines/languages/go
|
||||
|
||||
trigger:
|
||||
- master
|
||||
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
|
||||
variables:
|
||||
GOBIN: '$(GOPATH)/bin' # Go binaries path
|
||||
GOROOT: '/usr/local/go1.11' # Go installation path
|
||||
GOPATH: '$(system.defaultWorkingDirectory)/gopath' # Go workspace path
|
||||
modulePath: '$(GOPATH)/src/github.com/$(build.repository.name)' # Path to the module's code
|
||||
|
||||
steps:
|
||||
- script: |
|
||||
mkdir -p '$(GOBIN)'
|
||||
mkdir -p '$(GOPATH)/pkg'
|
||||
mkdir -p '$(modulePath)'
|
||||
shopt -s extglob
|
||||
shopt -s dotglob
|
||||
mv !(gopath) '$(modulePath)'
|
||||
echo '##vso[task.prependpath]$(GOBIN)'
|
||||
echo '##vso[task.prependpath]$(GOROOT)/bin'
|
||||
displayName: 'Set up the Go workspace'
|
||||
|
||||
- script: |
|
||||
go version
|
||||
go get -v -t -d ./...
|
||||
if [ -f Gopkg.toml ]; then
|
||||
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
||||
dep ensure
|
||||
fi
|
||||
go build -v .
|
||||
workingDirectory: '$(modulePath)'
|
||||
displayName: 'Get dependencies, then build'
|
||||
|
||||
- script: |
|
||||
go test -v .
|
||||
GOARCH=386 go test -v .
|
||||
workingDirectory: '$(modulePath)'
|
||||
displayName: 'run unittest'
|
||||
|
||||
- script: |
|
||||
GO111MODULE=on go run example/main.go &&
|
||||
GO111MODULE=on go run example/unwind.go &&
|
||||
GO111MODULE=on go run example/example.go &&
|
||||
GO111MODULE=on go run example/example2.go &&
|
||||
GO111MODULE=on go run example/example3.go
|
||||
workingDirectory: '$(modulePath)'
|
||||
displayName: 'run example'
|
|
@ -0,0 +1,9 @@
|
|||
#! /bin/bash
|
||||
|
||||
for i in {1..10000};do
|
||||
go test -gcflags=all='-l' -cover -o t
|
||||
if [[ "$?" != "0" ]]; then
|
||||
exit 233
|
||||
fi
|
||||
echo "@@@@@@@@@@@@@ ${i}th run done @@@@@@@@@@@@@@@"
|
||||
done
|
|
@ -0,0 +1,75 @@
|
|||
package gohook
|
||||
|
||||
import (
|
||||
"debug/elf"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
)
|
||||
|
||||
var (
|
||||
curExecutable, _ = filepath.Abs(os.Args[0])
|
||||
)
|
||||
|
||||
type SymbolSlice []elf.Symbol
|
||||
|
||||
func (a SymbolSlice) Len() int { return len(a) }
|
||||
func (a SymbolSlice) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a SymbolSlice) Less(i, j int) bool { return a[i].Value < a[j].Value }
|
||||
|
||||
type ElfInfo struct {
|
||||
CurFile string
|
||||
Symbol SymbolSlice
|
||||
}
|
||||
|
||||
func NewElfInfo() (*ElfInfo, error) {
|
||||
ei := &ElfInfo{CurFile: curExecutable}
|
||||
err := ei.init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ei, nil
|
||||
}
|
||||
|
||||
func (ei *ElfInfo) init() error {
|
||||
f, err := elf.Open(ei.CurFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
var sym []elf.Symbol
|
||||
sym, err = f.Symbols()
|
||||
ei.Symbol = make(SymbolSlice, 0, len(sym))
|
||||
|
||||
for _, v := range sym {
|
||||
if v.Size > 0 {
|
||||
ei.Symbol = append(ei.Symbol, v)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sort.Sort(ei.Symbol)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ei *ElfInfo) GetFuncSize(addr uintptr) (uint32, error) {
|
||||
if ei.Symbol == nil {
|
||||
return 0, errors.New("no symbol")
|
||||
}
|
||||
|
||||
i := sort.Search(len(ei.Symbol), func(i int) bool { return ei.Symbol[i].Value >= uint64(addr) })
|
||||
if i < len(ei.Symbol) && ei.Symbol[i].Value == uint64(addr) {
|
||||
//fmt.Printf("addr:0x%x,value:0x%x, sz:%d\n", addr, ei.Symbol[i].Value, ei.Symbol[i].Size)
|
||||
return uint32(ei.Symbol[i].Size), nil
|
||||
}
|
||||
|
||||
//fmt.Printf("not find elf\n")
|
||||
return 0, errors.New("can not find func")
|
||||
}
|
|
@ -0,0 +1,299 @@
|
|||
package gohook
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func victim(a, b, c int, e, f, g string) int {
|
||||
if a > 100 {
|
||||
return 42
|
||||
}
|
||||
|
||||
var someBigStackArray [4096]byte // to occupy stack, don't let it escape
|
||||
for i := 0; i < len(someBigStackArray); i++ {
|
||||
someBigStackArray[i] = byte((a ^ b) & (i ^ c))
|
||||
}
|
||||
|
||||
ch := make(chan int, 2)
|
||||
|
||||
if (a % 2) != 0 {
|
||||
someBigStackArray[200] = 0xe9
|
||||
}
|
||||
|
||||
ch <- 2
|
||||
fmt.Printf("calling real victim() (%s,%s,%s,%x):%dth\n", e, f, g, someBigStackArray[200], a)
|
||||
|
||||
runtime.GC()
|
||||
|
||||
return victim(a+1, b-1, c-1, e, f, g)
|
||||
}
|
||||
|
||||
func victimTrampoline(a, b, c int, e, f, g string) int {
|
||||
fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23)
|
||||
fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23)
|
||||
fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23)
|
||||
fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23)
|
||||
fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23)
|
||||
fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23)
|
||||
fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23)
|
||||
fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23)
|
||||
fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23)
|
||||
fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23)
|
||||
fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23)
|
||||
fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23)
|
||||
|
||||
for {
|
||||
if (a % 2) != 0 {
|
||||
fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23)
|
||||
} else {
|
||||
a++
|
||||
}
|
||||
|
||||
if a+b > 100 {
|
||||
break
|
||||
}
|
||||
|
||||
buff := bytes.NewBufferString("something weird")
|
||||
fmt.Printf("len:%d\n", buff.Len())
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
func victimReplace(a, b, c int, e, f, g string) int {
|
||||
fmt.Printf("victimReplace sends its regard\n")
|
||||
ret := 0
|
||||
if a > 100 {
|
||||
ret = 100000
|
||||
}
|
||||
|
||||
return ret + victimTrampoline(a, b, c, e, f, g)
|
||||
}
|
||||
|
||||
func TestStackGrowth(t *testing.T) {
|
||||
SetMinJmpCodeSize(64)
|
||||
defer SetMinJmpCodeSize(0)
|
||||
|
||||
ResetFuncPrologue()
|
||||
|
||||
err := Hook(victim, victimReplace, victimTrampoline)
|
||||
assert.Nil(t, err)
|
||||
|
||||
ret := victim(0, 1000, 100000, "ab", "miliao", "see")
|
||||
|
||||
runtime.GC()
|
||||
runtime.GC()
|
||||
runtime.GC()
|
||||
|
||||
assert.Equal(t, 100042, ret)
|
||||
|
||||
UnHook(victim)
|
||||
|
||||
fmt.Printf("after unHook\n")
|
||||
victimReplace(98, 2, 3, "ab", "ef", "g")
|
||||
}
|
||||
|
||||
func TestFuncSize(t *testing.T) {
|
||||
ResetFuncPrologue()
|
||||
|
||||
addr1 := GetFuncAddr(victim)
|
||||
addr2 := GetFuncAddr(victimReplace)
|
||||
addr3 := GetFuncAddr(victimTrampoline)
|
||||
|
||||
elf, err := NewElfInfo()
|
||||
hasElf := (err == nil)
|
||||
|
||||
sz11, err11 := GetFuncSizeByGuess(GetArchMode(), addr1, true)
|
||||
assert.Nil(t, err11)
|
||||
|
||||
if hasElf {
|
||||
sz1, err1 := elf.GetFuncSize(addr1)
|
||||
assert.Nil(t, err1)
|
||||
assert.Equal(t, sz1, sz11)
|
||||
} else {
|
||||
assert.True(t, sz11 > 0)
|
||||
}
|
||||
|
||||
sz21, err21 := GetFuncSizeByGuess(GetArchMode(), addr2, true)
|
||||
assert.Nil(t, err21)
|
||||
|
||||
if hasElf {
|
||||
sz2, err2 := elf.GetFuncSize(addr2)
|
||||
assert.Nil(t, err2)
|
||||
assert.Equal(t, sz2, sz21)
|
||||
}
|
||||
|
||||
sz31, err31 := GetFuncSizeByGuess(GetArchMode(), addr3, true)
|
||||
assert.Nil(t, err31)
|
||||
|
||||
if hasElf {
|
||||
sz3, err3 := elf.GetFuncSize(addr3)
|
||||
assert.Nil(t, err3)
|
||||
|
||||
assert.Equal(t, sz3, sz31)
|
||||
}
|
||||
}
|
||||
|
||||
func mySprintf(format string, a ...interface{}) string {
|
||||
addr1 := GetFuncAddr(victim)
|
||||
addr2 := GetFuncAddr(victimReplace)
|
||||
addr3 := GetFuncAddr(victimTrampoline)
|
||||
|
||||
elf, err := NewElfInfo()
|
||||
fmt.Println("show:", elf, err)
|
||||
|
||||
sz1, err1 := elf.GetFuncSize(addr1)
|
||||
fmt.Println("show:", sz1, err1)
|
||||
|
||||
sz11, err11 := GetFuncSizeByGuess(GetArchMode(), addr1, false)
|
||||
fmt.Println("show:", sz11, err11)
|
||||
|
||||
sz2, err2 := elf.GetFuncSize(addr2)
|
||||
fmt.Println("show:", sz2, err2)
|
||||
sz21, err21 := GetFuncSizeByGuess(GetArchMode(), addr2, false)
|
||||
fmt.Println("show:", sz21, err21)
|
||||
|
||||
sz3, err3 := elf.GetFuncSize(addr3)
|
||||
fmt.Println("show:", sz3, err3)
|
||||
sz31, err31 := GetFuncSizeByGuess(GetArchMode(), addr3, false)
|
||||
fmt.Println("show:", sz31, err31)
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func TestCopyFunc(t *testing.T) {
|
||||
ResetFuncPrologue()
|
||||
|
||||
addr := GetFuncAddr(mySprintf)
|
||||
sz := GetFuncInstSize(mySprintf)
|
||||
|
||||
tp := makeSliceFromPointer(addr, int(sz))
|
||||
txt := make([]byte, int(sz))
|
||||
copy(txt, tp)
|
||||
|
||||
fs := "some random text, from %d,%S,%T"
|
||||
s1 := fmt.Sprintf(fs, 233, "miliao test sprintf", addr)
|
||||
|
||||
info := &CodeInfo{}
|
||||
origin, err := CopyFunction(true, fmt.Sprintf, mySprintf, info)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, len(txt), len(origin))
|
||||
assert.Equal(t, txt, origin)
|
||||
|
||||
s2 := mySprintf(fs, 233, "miliao test sprintf", addr)
|
||||
|
||||
assert.Equal(t, s1, s2)
|
||||
|
||||
addr2 := GetFuncAddr(fmt.Sprintf)
|
||||
sz2, _ := GetFuncSizeByGuess(GetArchMode(), addr2, true)
|
||||
sz3, _ := GetFuncSizeByGuess(GetArchMode(), addr, true)
|
||||
|
||||
assert.Equal(t, sz2, sz3)
|
||||
}
|
||||
|
||||
type DataHolder struct {
|
||||
size int
|
||||
addr []string
|
||||
close chan int
|
||||
channel chan *DataHolder
|
||||
}
|
||||
|
||||
func foo_return_orig(from *DataHolder, addr []string) *DataHolder {
|
||||
dh := &DataHolder{
|
||||
size: from.size + 8,
|
||||
close: make(chan int, from.size+2),
|
||||
channel: make(chan *DataHolder, from.size+2),
|
||||
}
|
||||
|
||||
// origin func doesn't store argument 'addr'
|
||||
|
||||
runtime.GC()
|
||||
runtime.GC()
|
||||
from.close = make(chan int, from.size+3)
|
||||
fmt.Printf("done foo_return_orig\n")
|
||||
return dh
|
||||
}
|
||||
|
||||
func foo_return_replace(from *DataHolder, addr []string) *DataHolder {
|
||||
dummy := make([]int, 10*1024*1024)
|
||||
fmt.Printf("dummy data, size:%d\n", len(dummy))
|
||||
|
||||
// replace func DOES store argument 'addr'
|
||||
/// this will trick golang escape analysis to make wrong decisions regarding whether to heap allocate 'addr'
|
||||
|
||||
dh := &DataHolder{
|
||||
addr: addr,
|
||||
size: from.size,
|
||||
close: make(chan int, from.size),
|
||||
channel: make(chan *DataHolder, from.size+1),
|
||||
}
|
||||
|
||||
// fixing escape analysis by always deep copy slice.
|
||||
dh.addr = make([]string, len(addr))
|
||||
copy(dh.addr, addr)
|
||||
|
||||
origin := foo_return_trampoline(from, addr)
|
||||
from.close = make(chan int, origin.size)
|
||||
|
||||
go func() {
|
||||
select {
|
||||
case <-dh.close:
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
runtime.GC()
|
||||
from.channel = make(chan *DataHolder, 22)
|
||||
runtime.GC()
|
||||
runtime.GC()
|
||||
runtime.GC()
|
||||
runtime.GC()
|
||||
fmt.Printf("done foo_return_replace\n")
|
||||
return dh
|
||||
}
|
||||
|
||||
func foo_return_trampoline(from *DataHolder, addr []string) *DataHolder {
|
||||
for {
|
||||
if (from.size % 2) != 0 {
|
||||
fmt.Printf("even from size:%d\n", from.size)
|
||||
} else {
|
||||
from.size++
|
||||
}
|
||||
|
||||
if from.size > 100 {
|
||||
break
|
||||
}
|
||||
|
||||
buff := bytes.NewBufferString("something weird")
|
||||
fmt.Printf("len:%d\n", buff.Len())
|
||||
}
|
||||
|
||||
from.close = make(chan int, 11)
|
||||
return from
|
||||
}
|
||||
|
||||
func TestGarbageCollection(t *testing.T) {
|
||||
err := Hook(foo_return_orig, foo_return_replace, foo_return_trampoline)
|
||||
assert.Nil(t, err)
|
||||
|
||||
addr := []string{"mm11111111111vvvvvvvv", "=ggslsllllllllllllllll"}
|
||||
|
||||
dh := &DataHolder{size: 32}
|
||||
dh2 := foo_return_orig(dh, addr)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
runtime.GC()
|
||||
runtime.GC()
|
||||
assert.NotNil(t, dh2)
|
||||
runtime.GC()
|
||||
runtime.GC()
|
||||
}
|
||||
|
||||
dh.close <- 1
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
module github.com/brahma-adshonor/gohook
|
||||
|
||||
go 1.11
|
||||
|
||||
require (
|
||||
github.com/stretchr/testify v1.3.0
|
||||
golang.org/x/arch v0.0.0-20190312162104-788fe5ffcd8c
|
||||
)
|
|
@ -0,0 +1,10 @@
|
|||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
golang.org/x/arch v0.0.0-20190312162104-788fe5ffcd8c h1:Rx/HTKi09myZ25t1SOlDHmHOy/mKxNAcu0hP1oPX9qM=
|
||||
golang.org/x/arch v0.0.0-20190312162104-788fe5ffcd8c/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
|
@ -0,0 +1,172 @@
|
|||
package gohook
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type HookInfo struct {
|
||||
Mode int
|
||||
Info *CodeInfo
|
||||
Target reflect.Value
|
||||
Replacement reflect.Value
|
||||
Trampoline reflect.Value
|
||||
}
|
||||
|
||||
var (
|
||||
archMode = 64
|
||||
g_all = make(map[uintptr]HookInfo)
|
||||
)
|
||||
|
||||
func init() {
|
||||
sz := unsafe.Sizeof(uintptr(0))
|
||||
if sz == 4 {
|
||||
archMode = 32
|
||||
}
|
||||
}
|
||||
|
||||
func GetArchMode() int {
|
||||
return archMode
|
||||
}
|
||||
|
||||
// valueStruct is taken from runtime source code.
|
||||
// it may be changed in later release
|
||||
type valueStruct struct {
|
||||
typ uintptr
|
||||
ptr uintptr
|
||||
}
|
||||
|
||||
func getDataPtrFromValue(v reflect.Value) uintptr {
|
||||
return (uintptr)((*valueStruct)(unsafe.Pointer(&v)).ptr)
|
||||
}
|
||||
|
||||
func ShowDebugInfo() string {
|
||||
buff := bytes.NewBuffer(make([]byte, 0, 256))
|
||||
for k, v := range g_all {
|
||||
s := fmt.Sprintf("hook function at addr:%x, how:%s, num of instruction fixed:%d\n", k, v.Info.How, len(v.Info.Fix))
|
||||
|
||||
buff.WriteString(s)
|
||||
for _, f := range v.Info.Fix {
|
||||
s = fmt.Sprintf("==@%08x new inst:", f.Addr)
|
||||
buff.WriteString(s)
|
||||
for _, c := range f.Code {
|
||||
s = fmt.Sprintf("%02x ", c)
|
||||
buff.WriteString(s)
|
||||
}
|
||||
s = fmt.Sprintf("\n")
|
||||
buff.WriteString(s)
|
||||
}
|
||||
}
|
||||
|
||||
return string(buff.Bytes())
|
||||
}
|
||||
|
||||
func Hook(target, replacement, trampoline interface{}) error {
|
||||
t := reflect.ValueOf(target)
|
||||
r := reflect.ValueOf(replacement)
|
||||
t2 := reflect.ValueOf(trampoline)
|
||||
return doHook(archMode, false, t, r, t2)
|
||||
}
|
||||
|
||||
func HookByIndirectJmp(target, replacement, trampoline interface{}) error {
|
||||
t := reflect.ValueOf(target)
|
||||
r := reflect.ValueOf(replacement)
|
||||
t2 := reflect.ValueOf(trampoline)
|
||||
return doHook(archMode, true, t, r, t2)
|
||||
}
|
||||
|
||||
func UnHook(target interface{}) error {
|
||||
t := reflect.ValueOf(target)
|
||||
return doUnHook(t.Pointer())
|
||||
}
|
||||
|
||||
func HookMethod(instance interface{}, method string, replacement, trampoline interface{}) error {
|
||||
target := reflect.TypeOf(instance)
|
||||
m, ok := target.MethodByName(method)
|
||||
if !ok {
|
||||
return fmt.Errorf("unknown method %s.%s()", target.Name(), method)
|
||||
}
|
||||
r := reflect.ValueOf(replacement)
|
||||
t := reflect.ValueOf(trampoline)
|
||||
return doHook(archMode, false, m.Func, r, t)
|
||||
}
|
||||
|
||||
func UnHookMethod(instance interface{}, methodName string) error {
|
||||
target := reflect.TypeOf(instance)
|
||||
m, ok := target.MethodByName(methodName)
|
||||
if !ok {
|
||||
return errors.New(fmt.Sprintf("unknown method %s", methodName))
|
||||
}
|
||||
|
||||
return UnHook(m.Func.Interface())
|
||||
}
|
||||
|
||||
func doUnHook(target uintptr) error {
|
||||
info, ok := g_all[target]
|
||||
if !ok {
|
||||
return errors.New("target not exist")
|
||||
}
|
||||
|
||||
CopyInstruction(target, info.Info.Origin)
|
||||
|
||||
if info.Info.How == "fix" {
|
||||
for _, v := range info.Info.Fix {
|
||||
CopyInstruction(v.Addr, v.Code)
|
||||
}
|
||||
}
|
||||
|
||||
if info.Trampoline.IsValid() {
|
||||
CopyInstruction(info.Trampoline.Pointer(), info.Info.TrampolineOrig)
|
||||
}
|
||||
|
||||
delete(g_all, target)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func doHook(mode int, rdxIndirect bool, target, replacement, trampoline reflect.Value) error {
|
||||
if target.Kind() != reflect.Func {
|
||||
return fmt.Errorf("target must be a Func")
|
||||
}
|
||||
|
||||
if replacement.Kind() != reflect.Func {
|
||||
return fmt.Errorf("replacement must be a Func")
|
||||
}
|
||||
|
||||
if target.Type() != replacement.Type() {
|
||||
return fmt.Errorf("target and replacement must have the same type %s != %s", target.Type().Name(), replacement.Type().Name())
|
||||
}
|
||||
|
||||
tp := uintptr(0)
|
||||
if trampoline.IsValid() {
|
||||
if trampoline.Kind() != reflect.Func {
|
||||
return fmt.Errorf("replacement must be a Func")
|
||||
}
|
||||
|
||||
if target.Type() != trampoline.Type() {
|
||||
return fmt.Errorf("target and trampoline must have the same type %s != %s", target.Type().Name(), trampoline.Type().Name())
|
||||
}
|
||||
|
||||
tp = trampoline.Pointer()
|
||||
}
|
||||
|
||||
doUnHook(target.Pointer())
|
||||
|
||||
replaceAddr := replacement.Pointer()
|
||||
if rdxIndirect {
|
||||
// get data ptr out of a reflect value.
|
||||
replaceAddr = getDataPtrFromValue(replacement)
|
||||
}
|
||||
|
||||
info, err := hookFunction(mode, rdxIndirect, target.Pointer(), replaceAddr, tp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
g_all[target.Pointer()] = HookInfo{Mode: mode, Info: info, Target: target, Replacement: replacement, Trampoline: trampoline}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,273 @@
|
|||
package gohook
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func myPrintf(f string, a ...interface{}) (n int, err error) {
|
||||
myPrintfTramp("prefixed by miliao -- ")
|
||||
return myPrintfTramp(f, a...)
|
||||
}
|
||||
|
||||
//go:noinline
|
||||
func myPrintfTramp(f string, a ...interface{}) (n int, err error) {
|
||||
fmt.Printf("hello")
|
||||
fmt.Printf("hello")
|
||||
fmt.Printf("hello")
|
||||
fmt.Printf("hello")
|
||||
fmt.Printf("hello")
|
||||
return fmt.Printf("hello")
|
||||
}
|
||||
|
||||
func init() {
|
||||
fmt.Printf("test file init()\n")
|
||||
err := Hook(fmt.Printf, myPrintf, myPrintfTramp)
|
||||
if err != nil {
|
||||
fmt.Printf("err:%s\n", err.Error())
|
||||
} else {
|
||||
fmt.Printf("hook fmt.Printf() done\n")
|
||||
}
|
||||
|
||||
fmt.Printf("debug info for init():%s\n", ShowDebugInfo())
|
||||
}
|
||||
|
||||
//go:noinline
|
||||
func foo1(v1 int, v2 string) int {
|
||||
|
||||
fmt.Printf("foo1:%d(%s)\n", v1, v2)
|
||||
return v1 + 42
|
||||
}
|
||||
|
||||
func foo2(v1 int, v2 string) int {
|
||||
fmt.Printf("foo2:%d(%s)\n", v1, v2)
|
||||
v1 = foo3(100, "not calling foo3")
|
||||
return v1 + 4200
|
||||
}
|
||||
|
||||
//go:noinline
|
||||
func foo3(v1 int, v2 string) int {
|
||||
fmt.Printf("foo3:%d(%s)\n", v1, v2)
|
||||
return v1 + 10000
|
||||
}
|
||||
|
||||
func myByteContain(a, b []byte) bool {
|
||||
fmt.Printf("calling fake bytes.Contain()\n")
|
||||
return false
|
||||
}
|
||||
|
||||
func TestHook(t *testing.T) {
|
||||
ResetFuncPrologue()
|
||||
|
||||
fmt.Printf("start testing...\n")
|
||||
|
||||
ret1 := foo1(23, "sval for foo1")
|
||||
assert.Equal(t, 65, ret1)
|
||||
|
||||
err := Hook(foo1, foo2, foo3)
|
||||
assert.Nil(t, err)
|
||||
|
||||
ret2 := foo1(23, "sval for foo1")
|
||||
assert.Equal(t, 4342, ret2)
|
||||
|
||||
ret4 := foo3(100, "vvv")
|
||||
assert.Equal(t, 142, ret4)
|
||||
|
||||
UnHook(foo1)
|
||||
ret3 := foo1(23, "sval for foo1")
|
||||
assert.Equal(t, 65, ret3)
|
||||
|
||||
ret5 := foo3(100, "vvv")
|
||||
assert.Equal(t, 10100, ret5)
|
||||
|
||||
ret6 := bytes.Contains([]byte{1, 2, 3}, []byte{2, 3})
|
||||
assert.Equal(t, true, ret6)
|
||||
err = Hook(bytes.Contains, myByteContain, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
fun := bytes.Contains // prevent inline
|
||||
ret7 := fun([]byte{1, 2, 3}, []byte{2, 3})
|
||||
|
||||
assert.Equal(t, false, ret7)
|
||||
UnHook(bytes.Contains)
|
||||
ret8 := bytes.Contains([]byte{1, 2, 3}, []byte{2, 3})
|
||||
assert.Equal(t, true, ret8)
|
||||
}
|
||||
|
||||
func myBuffLen(b *bytes.Buffer) int {
|
||||
fmt.Println("calling myBuffLen")
|
||||
return 0 + myBuffLenTramp(b)
|
||||
}
|
||||
|
||||
//go:noinline
|
||||
func myBuffLenTramp(b *bytes.Buffer) int {
|
||||
fmt.Println("calling myBuffLenTramp")
|
||||
return 1000
|
||||
}
|
||||
|
||||
func myBuffGrow(b *bytes.Buffer, n int) {
|
||||
fmt.Println("fake buffer grow func")
|
||||
}
|
||||
|
||||
func myBuffWriteString(b *bytes.Buffer, s string) (int, error) {
|
||||
fmt.Printf("fake buffer WriteString func, s:%s\n", s)
|
||||
|
||||
l, _ := myBuffWriteStringTramp(b, s)
|
||||
return 1000 + l, nil
|
||||
}
|
||||
|
||||
func myBuffWriteStringTramp(b *bytes.Buffer, s string) (int, error) {
|
||||
fmt.Printf("fake buffer WriteString tramp, s:%s\n", s)
|
||||
fmt.Printf("fake buffer WriteString tramp, s:%s\n", s)
|
||||
fmt.Printf("fake buffer WriteString tramp, s:%s\n", s)
|
||||
fmt.Printf("fake buffer WriteString tramp, s:%s\n", s)
|
||||
fmt.Printf("fake buffer WriteString tramp, s:%s\n", s)
|
||||
fmt.Printf("fake buffer WriteString tramp, s:%s\n", s)
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func TestInstanceHook(t *testing.T) {
|
||||
ResetFuncPrologue()
|
||||
buff1 := bytes.NewBufferString("abcd")
|
||||
assert.Equal(t, 4, buff1.Len())
|
||||
|
||||
err1 := HookMethod(buff1, "Grow", myBuffGrow, nil)
|
||||
err2 := HookMethod(buff1, "Len", myBuffLen, myBuffLenTramp)
|
||||
|
||||
assert.Nil(t, err1)
|
||||
assert.Nil(t, err2)
|
||||
|
||||
assert.Equal(t, 4, buff1.Len()) // Len() is inlined
|
||||
buff1.Grow(233) // no grow
|
||||
assert.Equal(t, 4, buff1.Len()) // Len() is inlined
|
||||
|
||||
err3 := HookMethod(buff1, "WriteString", myBuffWriteString, myBuffWriteStringTramp)
|
||||
assert.Nil(t, err3)
|
||||
|
||||
sz1, _ := buff1.WriteString("miliao")
|
||||
assert.Equal(t, 1006, sz1)
|
||||
assert.Equal(t, 10, buff1.Len()) // Len() is inlined
|
||||
|
||||
err4 := UnHookMethod(buff1, "WriteString")
|
||||
assert.Nil(t, err4)
|
||||
|
||||
flen := buff1.Len
|
||||
|
||||
sz2, _ := buff1.WriteString("miliao")
|
||||
assert.Equal(t, 6, sz2)
|
||||
assert.Equal(t, 16, flen()) // Len() is inlined
|
||||
|
||||
sz3, _ := myBuffWriteStringTramp(nil, "sssssss")
|
||||
assert.Equal(t, 0, sz3)
|
||||
}
|
||||
|
||||
//go:noinline
|
||||
func foov(v int) int {
|
||||
v2 := v*v + 2*v
|
||||
return v + v2
|
||||
}
|
||||
|
||||
func foor(v int) int {
|
||||
if v%2 == 0 {
|
||||
fmt.Printf("vvvvvvv:%d\n", v)
|
||||
}
|
||||
|
||||
return v + 1 + foot(v)
|
||||
}
|
||||
|
||||
//go:noinline
|
||||
func foot(v int) int {
|
||||
fmt.Printf("fake func hold:%+v\n", v)
|
||||
fmt.Printf("fake func hold:%+v\n", v)
|
||||
fmt.Printf("fake func hold:%+v\n", v)
|
||||
if v%3 == 0 {
|
||||
panic("vvvv")
|
||||
} else {
|
||||
v = v*v + 23
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
func TestHookByIndirectJmp(t *testing.T) {
|
||||
ResetFuncPrologue()
|
||||
|
||||
v := foov(3)
|
||||
assert.Equal(t, 3*3+2*3+3, v)
|
||||
|
||||
err := HookByIndirectJmp(foov, foor, foot)
|
||||
assert.Nil(t, err)
|
||||
|
||||
v2 := foov(3)
|
||||
assert.Equal(t, 3+1+v, v2)
|
||||
}
|
||||
|
||||
type cfunc func(int) int
|
||||
|
||||
func getClosureFunc(v int) (cfunc, cfunc) {
|
||||
vv := v + 1
|
||||
|
||||
f1 := func(v2 int) int {
|
||||
fmt.Printf("f111111, v:%d\n", v2) // prevent inline
|
||||
v3 := v2*v2 + vv*v + v
|
||||
return v3
|
||||
}
|
||||
|
||||
vv2 := v + 10
|
||||
f2 := func(v2 int) int {
|
||||
return v2 + vv2 + v
|
||||
}
|
||||
|
||||
return f1, f2
|
||||
}
|
||||
|
||||
func TestClosure(t *testing.T) {
|
||||
ResetFuncPrologue()
|
||||
|
||||
f1, f2 := getClosureFunc(200)
|
||||
|
||||
// use Hook() will fail
|
||||
err := HookByIndirectJmp(f1, f2, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
v1 := f1(2)
|
||||
assert.Equal(t, 2+200+10+200, v1)
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
//go:noinline
|
||||
func tfunc(v int) int {
|
||||
fmt.Printf("vvvvv:%v\n", v)
|
||||
return v*v*100 + 123 + v
|
||||
}
|
||||
|
||||
func TestMakeFunc(t *testing.T) {
|
||||
ResetFuncPrologue()
|
||||
|
||||
replace := func(in []reflect.Value) []reflect.Value {
|
||||
v := int(in[0].Int())
|
||||
ret := reflect.ValueOf(v + 1000)
|
||||
return []reflect.Value{ret}
|
||||
}
|
||||
|
||||
f := reflect.MakeFunc(reflect.ValueOf(tfunc).Type(), replace)
|
||||
|
||||
// use Hook() will panic
|
||||
err := HookByIndirectJmp(tfunc, f.Interface(), nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
v := tfunc(int(3))
|
||||
assert.Equal(t, 3+1000, v)
|
||||
}
|
|
@ -0,0 +1,391 @@
|
|||
package gohook
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func inplaceFix(a, b, c int, e, f, g string) int {
|
||||
fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23)
|
||||
fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23)
|
||||
fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23)
|
||||
fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23)
|
||||
fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23)
|
||||
fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23)
|
||||
fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23)
|
||||
fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23)
|
||||
fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23)
|
||||
fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23)
|
||||
|
||||
for {
|
||||
if (a % 2) != 0 {
|
||||
fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23)
|
||||
} else {
|
||||
a++
|
||||
}
|
||||
|
||||
if a+b > 100 {
|
||||
break
|
||||
}
|
||||
|
||||
buff := bytes.NewBufferString("something weird")
|
||||
fmt.Printf("len:%d\n", buff.Len())
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
func TestRipRelativeAddr(t *testing.T) {
|
||||
toAddr := uintptr(0x10101042)
|
||||
code := []byte{0x48, 0x8b, 0x05, 0x11, 0x22, 0x33, 0x44}
|
||||
addr := calcJumpToAbsAddr(GetArchMode(), toAddr, code)
|
||||
|
||||
assert.Equal(t, toAddr+7+0x44332211, addr)
|
||||
}
|
||||
|
||||
func TestFixInplace(t *testing.T) {
|
||||
d0 := int32(0x01b1)
|
||||
d1 := byte(0xb2) // byte(-78)
|
||||
d2 := byte(0xa8) // byte(-88)
|
||||
|
||||
prefix1 := []byte{
|
||||
0x64, 0x48, 0x8b, 0x0c, 0x25, 0xf8, 0xff, 0xff, 0xff, // 9
|
||||
0x48, 0x3b, 0x61, 0x10, // 4
|
||||
}
|
||||
|
||||
jc0 := []byte{
|
||||
0x0f, 0x86, byte(d0), byte(d0 >> 8), 0x00, 0x00, // jmp
|
||||
}
|
||||
|
||||
prefix2 := []byte{
|
||||
0x48, 0x83, 0xec, 0x58, // 4
|
||||
0x48, 0x89, 0x6c, 0x24, 0x50, // 5
|
||||
0x48, 0x8d, 0x6c, 0x24, 0x50, // 5
|
||||
0x90, // 1
|
||||
}
|
||||
|
||||
prefix3rip := []byte{
|
||||
0x48, 0x8b, 0x05, 0xc7, 0x5f, 0x16, 0x00, // 7 mov $rip offset
|
||||
}
|
||||
|
||||
prefix4rip := []byte{
|
||||
0x48, 0x8d, 0x0d, 0x50, 0x85, 0x07, 0x00, // 7 lea $rip offset
|
||||
}
|
||||
|
||||
prefix5 := []byte{
|
||||
0x48, 0x89, 0x0c, 0x24, // 4
|
||||
0x48, 0x89, 0x44, 0x24, 0x08, // 5
|
||||
}
|
||||
|
||||
prefix6rip := []byte{
|
||||
0x48, 0x8d, 0x05, 0x9f, 0xe0, 0x04, 0x00, // 7 lea $rip offset
|
||||
}
|
||||
|
||||
prefix7 := []byte{
|
||||
0x48, 0x89, 0x44, 0x24, 0x10, // 5
|
||||
0x48, 0xc7, 0x44, 0x24, 0x18, 0x07, 0x00, 0x00, 0x00, // 9
|
||||
}
|
||||
// totoal 78 bytes
|
||||
|
||||
// short jump
|
||||
jc1 := []byte{0xeb, d1} // 2
|
||||
|
||||
mid := []byte{
|
||||
0x0f, 0x57, 0xc0, // 3
|
||||
0x0f, 0x11, 0x44, 0x24, 0x28, // 5
|
||||
}
|
||||
|
||||
jc2 := []byte{
|
||||
// condition jump
|
||||
0x77, d2, // 2
|
||||
}
|
||||
|
||||
posfix := []byte{
|
||||
// trailing
|
||||
0xcc, 0xcc, 0xcc, 0xcc,
|
||||
0xcc, 0xcc, 0xcc, 0xcc,
|
||||
0xcc, 0xcc, 0xcc, 0xcc,
|
||||
0xcc, 0xcc, 0xcc, 0xcc,
|
||||
}
|
||||
|
||||
var fc []byte
|
||||
all := [][]byte{prefix1, jc0, prefix2, prefix3rip, prefix4rip, prefix5, prefix6rip, prefix7, jc1, mid, jc2, posfix}
|
||||
for k := range all {
|
||||
fc = append(fc, all[k]...)
|
||||
}
|
||||
|
||||
info := &CodeInfo{}
|
||||
addr := GetFuncAddr(inplaceFix)
|
||||
size := len(fc)
|
||||
mvSize := 0x09
|
||||
toAddr := GetFuncAddr(inplaceFix2)
|
||||
|
||||
curAddr1 := addr + uintptr(78)
|
||||
curAddr2 := addr + uintptr(78) + uintptr(10)
|
||||
|
||||
CopyInstruction(addr, fc)
|
||||
|
||||
fs := makeSliceFromPointer(addr, len(fc))
|
||||
raw := make([]byte, len(fc))
|
||||
copy(raw, fs)
|
||||
|
||||
fmt.Printf("src func:%x, target func:%x\n", addr, toAddr)
|
||||
|
||||
err := doFixFuncInplace(64, addr, toAddr, int(size), mvSize, info, 5)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, len(raw) >= len(info.Origin))
|
||||
raw = raw[:len(info.Origin)]
|
||||
assert.Equal(t, raw, info.Origin)
|
||||
assert.Equal(t, 18, len(info.Fix))
|
||||
assert.Equal(t, prefix1[:5], fs[:5])
|
||||
|
||||
off0 := d0 + 4
|
||||
fix0, _ := adjustInstructionOffset(jc0, int64(off0))
|
||||
fmt.Printf("inplace fix, off0:%x, sz:%d\n", off0, len(fix0))
|
||||
|
||||
rip3Addr := addr + uintptr(len(prefix1)+len(jc0)+len(prefix2))
|
||||
ripOff3 := calcJumpToAbsAddr(GetArchMode(), rip3Addr, prefix3rip) - uintptr(len(prefix3rip)) - rip3Addr + uintptr(4)
|
||||
prefix3ripFix, _ := adjustInstructionOffset(prefix3rip, int64(ripOff3))
|
||||
|
||||
rip4Addr := addr + uintptr(len(prefix1)+len(jc0)+len(prefix2)+len(prefix3rip))
|
||||
ripOff4 := calcJumpToAbsAddr(GetArchMode(), rip4Addr, prefix4rip) - uintptr(len(prefix4rip)) - rip4Addr + uintptr(4)
|
||||
prefix4ripFix, _ := adjustInstructionOffset(prefix4rip, int64(ripOff4))
|
||||
|
||||
rip6Addr := addr + uintptr(len(prefix1)+len(jc0)+len(prefix2)+len(prefix3rip)+len(prefix4rip)+len(prefix5))
|
||||
ripOff6 := calcJumpToAbsAddr(GetArchMode(), rip6Addr, prefix6rip) - uintptr(len(prefix6rip)) - rip6Addr + uintptr(4)
|
||||
prefix6ripFix, _ := adjustInstructionOffset(prefix6rip, int64(ripOff6))
|
||||
|
||||
to1 := curAddr1 + uintptr(2) + uintptr(int32(int8(d1)))
|
||||
newTo1 := toAddr + to1 - addr
|
||||
off1 := int64(newTo1 - (curAddr1 - uintptr(4)) - 5)
|
||||
fix1, _ := translateJump(off1, jc1)
|
||||
fmt.Printf("inplace fix, off1:%x, sz:%d\n", off1, len(fix1))
|
||||
|
||||
to2 := curAddr2 + uintptr(2) + uintptr(int32(int8(d2)))
|
||||
newTo2 := toAddr + to2 - addr
|
||||
off2 := int64(newTo2 - (curAddr2 + uintptr(3) - uintptr(4)) - 6)
|
||||
fix2, _ := translateJump(off2, jc2)
|
||||
fmt.Printf("inplace fix, off2:%x, sz:%d\n", off2, len(fix2))
|
||||
|
||||
all2 := [][]byte{prefix1[:5], prefix1[9:], fix0, prefix2, prefix3ripFix, prefix4ripFix, prefix5, prefix6ripFix, prefix7, fix1, mid, fix2, posfix}
|
||||
var fc2 []byte
|
||||
for k := range all2 {
|
||||
fc2 = append(fc2, all2[k]...)
|
||||
}
|
||||
assert.Equal(t, len(fc)-4+3+4, len(fc2))
|
||||
|
||||
fs = makeSliceFromPointer(addr, len(fc2)-len(posfix))
|
||||
assert.Equal(t, fc2[:len(fc2)-len(posfix)], fs)
|
||||
}
|
||||
|
||||
func inplaceFix2(a, b, c int, e, f, g string) int {
|
||||
fmt.Printf("calling inplacefix2()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23)
|
||||
fmt.Printf("calling inplacefix2()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23)
|
||||
fmt.Printf("calling inplacefix2()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23)
|
||||
fmt.Printf("calling inplacefix2()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23)
|
||||
fmt.Printf("calling inplacefix2()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23)
|
||||
fmt.Printf("calling inplacefix2()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23)
|
||||
fmt.Printf("calling inplacefix2()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23)
|
||||
fmt.Printf("calling inplacefix2()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23)
|
||||
fmt.Printf("calling inplacefix2()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23)
|
||||
fmt.Printf("calling inplacefix2()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23)
|
||||
|
||||
for {
|
||||
if (a % 2) != 0 {
|
||||
fmt.Printf("calling victim()(%d,%s,%s,%x):%dth\n", a, e, f, c, 0x23)
|
||||
} else {
|
||||
a++
|
||||
}
|
||||
|
||||
if a+b > 100 {
|
||||
break
|
||||
}
|
||||
|
||||
buff := bytes.NewBufferString("something weird")
|
||||
fmt.Printf("len:%d\n", buff.Len())
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
func foo_for_inplace_fix(id string) string {
|
||||
c := 0
|
||||
for {
|
||||
fmt.Printf("calling victim\n")
|
||||
if id == "miliao" {
|
||||
return "done"
|
||||
}
|
||||
|
||||
c++
|
||||
if c > len(id) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("len:%d\n", len(id))
|
||||
return id + "xxx"
|
||||
}
|
||||
|
||||
func foo_for_inplace_fix_delimiter(id string) string {
|
||||
for {
|
||||
fmt.Printf("calling victim trampoline")
|
||||
if id == "miliao" {
|
||||
return "done"
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
ret := "miliao"
|
||||
ret += foo_for_inplace_fix("test")
|
||||
ret += foo_for_inplace_fix("test")
|
||||
ret += foo_for_inplace_fix("test")
|
||||
ret += foo_for_inplace_fix("test")
|
||||
|
||||
fmt.Printf("len1:%d\n", len(id))
|
||||
fmt.Printf("len2:%d\n", len(ret))
|
||||
|
||||
return id + ret
|
||||
}
|
||||
|
||||
func foo_for_inplace_fix_replace(id string) string {
|
||||
c := 0
|
||||
for {
|
||||
fmt.Printf("calling foo_for_inplace_fix_replace\n")
|
||||
if id == "miliao" {
|
||||
return "done"
|
||||
}
|
||||
c++
|
||||
if c > len(id) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// TODO uncomment following
|
||||
foo_for_inplace_fix_trampoline("miliao")
|
||||
|
||||
fmt.Printf("len:%d\n", len(id))
|
||||
return id + "xxx2"
|
||||
}
|
||||
|
||||
func foo_for_inplace_fix_trampoline(id string) string {
|
||||
c := 0
|
||||
for {
|
||||
fmt.Printf("calling foo_for_inplace_fix_trampoline\n")
|
||||
if id == "miliao" {
|
||||
return "done"
|
||||
}
|
||||
c++
|
||||
if c > len(id) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("len:%d\n", len(id))
|
||||
return id + "xxx3"
|
||||
}
|
||||
|
||||
func TestInplaceFixAtMoveArea(t *testing.T) {
|
||||
code := []byte{
|
||||
/*
|
||||
0x48, 0x8b, 0x48, 0x08, // mov 0x8(%rax),%rcx
|
||||
0x74, 0x4, // jbe
|
||||
0x48, 0x8b, 0x48, 0x18, // sub 0x18(%rax), %rcx
|
||||
0x48, 0x89, 0x4c, 0x24, 0x10, // %rcx, 0x10(%rsp)
|
||||
0xc3, // retq
|
||||
0xcc, 0xcc,
|
||||
*/
|
||||
0x90, 0x90,
|
||||
0xeb, 0x04, // jmp 4
|
||||
0x90, 0x90, 0x90, 0x90, 0x90,
|
||||
0x90, 0x90, 0x90, 0x90, 0x90,
|
||||
0xc3,
|
||||
0x74, 0xf0, // jbe -16
|
||||
0xcc, 0xcc, 0xcc, 0xcc,
|
||||
0xcc, 0xcc, 0xcc, 0xcc,
|
||||
}
|
||||
|
||||
target := GetFuncAddr(foo_for_inplace_fix)
|
||||
replace := GetFuncAddr(foo_for_inplace_fix_replace)
|
||||
trampoline := GetFuncAddr(foo_for_inplace_fix_trampoline)
|
||||
|
||||
assert.True(t, isByteOverflow((int32)(trampoline-target)))
|
||||
|
||||
CopyInstruction(target, code)
|
||||
|
||||
fmt.Printf("short call target:%x, replace:%x, trampoline:%x\n", target, replace, trampoline)
|
||||
err1 := Hook(foo_for_inplace_fix, foo_for_inplace_fix_replace, foo_for_inplace_fix_trampoline)
|
||||
assert.Nil(t, err1)
|
||||
|
||||
fmt.Printf("debug info:%s\n", ShowDebugInfo())
|
||||
|
||||
msg1 := foo_for_inplace_fix("txt")
|
||||
|
||||
fmt.Printf("calling foo inplace fix func\n")
|
||||
|
||||
assert.Equal(t, "txtxxx2", msg1)
|
||||
|
||||
sz1 := 5
|
||||
na1 := trampoline + uintptr(2)
|
||||
ta1 := target + uintptr(2+5+4-3)
|
||||
off1 := ta1 - (na1 + uintptr(sz1))
|
||||
|
||||
sz2 := 6
|
||||
na2 := target + uintptr(15+3-3)
|
||||
ta2 := trampoline + uintptr(1)
|
||||
off2 := ta2 - (na2 + uintptr(sz2))
|
||||
|
||||
fmt.Printf("off1:%x, off2:%x\n", off1, off2)
|
||||
|
||||
ret := []byte{
|
||||
0x90, 0x90,
|
||||
0xe9, 0x74, 0xfc, 0xff, 0xff,
|
||||
0x90, 0x90, 0x90, 0x90, 0x90,
|
||||
0x90, 0x90, 0x90, 0x90, 0x90,
|
||||
0xc3,
|
||||
0x0f, 0x84, 0x80, 0x03, 0x00, 0x00,
|
||||
0xcc, 0xcc, 0xcc,
|
||||
}
|
||||
|
||||
ret[3] = byte(off1)
|
||||
ret[4] = byte(off1 >> 8)
|
||||
ret[5] = byte(off1 >> 16)
|
||||
ret[6] = byte(off1 >> 24)
|
||||
|
||||
ret[20] = byte(off2)
|
||||
ret[21] = byte(off2 >> 8)
|
||||
ret[22] = byte(off2 >> 16)
|
||||
ret[23] = byte(off2 >> 24)
|
||||
|
||||
fc1 := makeSliceFromPointer(target, len(ret))
|
||||
fc2 := makeSliceFromPointer(trampoline, len(ret))
|
||||
|
||||
assert.Equal(t, ret[:8], fc2[:8])
|
||||
assert.Equal(t, byte(0xe9), fc2[8])
|
||||
assert.Equal(t, ret[8:], fc1[5:len(ret)-3])
|
||||
|
||||
code2 := []byte{
|
||||
0x90, 0x90, 0x90, 0x90,
|
||||
0x74, 0x04,
|
||||
0x90, 0x90, 0x90, 0x90,
|
||||
0x90, 0x90, 0x90, 0x90, 0x90,
|
||||
0xc3, 0xcc, 0x90,
|
||||
}
|
||||
|
||||
err2 := UnHook(foo_for_inplace_fix)
|
||||
assert.Nil(t, err2)
|
||||
|
||||
msg2 := foo_for_inplace_fix_trampoline("txt")
|
||||
assert.Equal(t, "txtxxx3", msg2)
|
||||
|
||||
msg3 := foo_for_inplace_fix_replace("txt2")
|
||||
assert.Equal(t, "txt2xxx2", msg3)
|
||||
|
||||
CopyInstruction(target, code2)
|
||||
|
||||
fsz, _ := GetFuncSizeByGuess(GetArchMode(), target, false)
|
||||
assert.Equal(t, len(code2)-1, int(fsz))
|
||||
}
|
|
@ -0,0 +1,329 @@
|
|||
package gohook
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"unsafe"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetInsLenGreaterThan(t *testing.T) {
|
||||
ResetFuncPrologue()
|
||||
c1 := []byte{0x64, 0x48, 0x8b, 0x0c, 0x25, 0xf8}
|
||||
c2 := []byte{0x64, 0x48, 0x8b, 0x0c, 0x25, 0xf8, 0xff, 0xff, 0xff}
|
||||
|
||||
r1 := GetInsLenGreaterThan(64, c1, len(c1)-2)
|
||||
assert.Equal(t, 0, r1)
|
||||
r2 := GetInsLenGreaterThan(64, c2, len(c2)-2)
|
||||
assert.Equal(t, len(c2), r2)
|
||||
r22 := GetInsLenGreaterThan(64, c2, len(c2))
|
||||
assert.Equal(t, len(c2), r22)
|
||||
r23 := GetInsLenGreaterThan(64, c2, len(c2)+2)
|
||||
assert.Equal(t, 0, r23)
|
||||
|
||||
c3 := []byte{0x64, 0x48, 0x8b, 0x0c, 0x25, 0xf8, 0xff, 0xff, 0xff, 0x48, 0x3b, 0x41, 0x10}
|
||||
r3 := GetInsLenGreaterThan(64, c3, len(c2)+2)
|
||||
assert.Equal(t, len(c3), r3)
|
||||
r32 := GetInsLenGreaterThan(64, c3, len(c2)-2)
|
||||
assert.Equal(t, len(c2), r32)
|
||||
}
|
||||
|
||||
func TestFixOneInstructionForTwoByteJmp(t *testing.T) {
|
||||
ResetFuncPrologue()
|
||||
// jump from within patching erea to outside, negative fix
|
||||
c1 := []byte{0x75, 0x40} // jne 64
|
||||
|
||||
l1, t1, r1 := FixOneInstruction(64, false, 10, 12, c1, 100, 8)
|
||||
|
||||
assert.Equal(t, 2, l1)
|
||||
assert.Equal(t, FT_CondJmp, t1)
|
||||
assert.Equal(t, c1[0], r1[0])
|
||||
assert.Equal(t, int8(-26), int8(r1[1]))
|
||||
|
||||
// jump from within patching erea to outside, positive fix
|
||||
l2, t2, r2 := FixOneInstruction(64, false, 10, 12, c1, 26, 8)
|
||||
|
||||
assert.Equal(t, 2, l2)
|
||||
assert.Equal(t, FT_CondJmp, t2)
|
||||
assert.Equal(t, c1[0], r2[0])
|
||||
assert.Equal(t, int8(48), int8(r2[1]))
|
||||
|
||||
//overflow test
|
||||
l3, t3, r3 := FixOneInstruction(64, false, 10, 12, c1, 1000, 8)
|
||||
|
||||
assert.Equal(t, 2, l3)
|
||||
assert.Equal(t, FT_OVERFLOW, t3)
|
||||
assert.Equal(t, c1[0], r3[0])
|
||||
assert.Equal(t, c1[1], r3[1])
|
||||
|
||||
// overflow test2
|
||||
c32 := []byte{0x75, 0x7e} // jne 0x7e
|
||||
l32, t32, r32 := FixOneInstruction(64, false, 30, 32, c32, 10, 8)
|
||||
|
||||
assert.Equal(t, 2, l32)
|
||||
assert.Equal(t, FT_OVERFLOW, t32)
|
||||
assert.Equal(t, c32[0], r32[0])
|
||||
assert.Equal(t, c32[1], r32[1])
|
||||
|
||||
// jump from outside patching erea to outside of patching erea
|
||||
l4, t4, r4 := FixOneInstruction(64, false, 10, 18, c1, 100, 4)
|
||||
|
||||
assert.Equal(t, 2, l4)
|
||||
assert.Equal(t, FT_SKIP, t4)
|
||||
assert.Equal(t, c1[0], r4[0])
|
||||
assert.Equal(t, c1[1], r4[1])
|
||||
|
||||
// jump from outside patching erea to within patching erea
|
||||
c2 := []byte{0x75, 0xe6} // jne -26
|
||||
l5, t5, r5 := FixOneInstruction(64, false, 10, 38, c2, 100, 8)
|
||||
|
||||
assert.Equal(t, 2, l5)
|
||||
assert.Equal(t, FT_CondJmp, t5)
|
||||
assert.Equal(t, c2[0], r5[0])
|
||||
assert.Equal(t, 64, int(r5[1]))
|
||||
|
||||
// jump within patching erea
|
||||
c3 := []byte{0x75, 0x06} // jne 6
|
||||
l6, t6, r6 := FixOneInstruction(64, false, 10, 12, c3, 100, 11)
|
||||
|
||||
assert.Equal(t, 2, l6)
|
||||
assert.Equal(t, FT_SKIP, t6)
|
||||
assert.Equal(t, c3[0], r6[0])
|
||||
assert.Equal(t, c3[1], r6[1])
|
||||
|
||||
// sign test, from outside to outside
|
||||
c4 := []byte{0x7c, 0xcd} // jne -51
|
||||
l7, t7, r7 := FixOneInstruction(64, false, 10, 83, c4, 1000, 10)
|
||||
|
||||
assert.Equal(t, 2, l7)
|
||||
assert.Equal(t, FT_SKIP, t7)
|
||||
assert.Equal(t, c4[0], r7[0])
|
||||
assert.Equal(t, c4[1], r7[1])
|
||||
}
|
||||
|
||||
func byteToInt32(d []byte) int32 {
|
||||
v := int32(uint32(d[0]) | (uint32(d[1]) << 8) | (uint32(d[2]) << 16) | (uint32(d[3]) << 24))
|
||||
return v
|
||||
}
|
||||
|
||||
func TestFixOneInstructionForSixByteJmp(t *testing.T) {
|
||||
ResetFuncPrologue()
|
||||
// jump from within patching erea to outside, negative fix
|
||||
c1 := []byte{0x0f, 0x8d, 0x10, 0x00, 0x00, 0x00} // jge 16
|
||||
|
||||
l1, t1, r1 := FixOneInstruction(64, false, 20, 22, c1, 100, 8)
|
||||
assert.Equal(t, 6, l1)
|
||||
assert.Equal(t, FT_CondJmp, t1)
|
||||
assert.Equal(t, c1[0], r1[0])
|
||||
assert.Equal(t, c1[1], r1[1])
|
||||
|
||||
assert.Equal(t, int32(-64), byteToInt32(r1[2:]))
|
||||
|
||||
// jump from within patching erea to outside, positive fix
|
||||
c2 := []byte{0x0f, 0x8d, 0x40, 0x00, 0x00, 0x00} // jge 64
|
||||
|
||||
l2, t2, r2 := FixOneInstruction(64, false, 2, 4, c2, 32, 9)
|
||||
assert.Equal(t, 6, l2)
|
||||
assert.Equal(t, FT_CondJmp, t2)
|
||||
assert.Equal(t, c2[0], r2[0])
|
||||
assert.Equal(t, c2[1], r2[1])
|
||||
|
||||
assert.Equal(t, int32(34), byteToInt32(r2[2:]))
|
||||
|
||||
// overflow test
|
||||
c3 := []byte{0x0f, 0x8d, 0xfe, 0xff, 0xff, 0x7f} // jge 64
|
||||
|
||||
l3, t3, r3 := FixOneInstruction(64, false, 10000, 10004, c3, 100, 16)
|
||||
assert.Equal(t, 6, l3)
|
||||
assert.Equal(t, FT_OVERFLOW, t3)
|
||||
assert.Equal(t, c3[0], r3[0])
|
||||
assert.Equal(t, c3[1], r3[1])
|
||||
assert.Equal(t, c3[2], r3[2])
|
||||
assert.Equal(t, c3[3], r3[3])
|
||||
assert.Equal(t, c3[4], r3[4])
|
||||
assert.Equal(t, c3[5], r3[5])
|
||||
|
||||
// jump from outside patching erea to outside of patching erea
|
||||
c4 := []byte{0x0f, 0x8d, 0x40, 0x00, 0x00, 0x00} // jge 64
|
||||
|
||||
l4, t4, r4 := FixOneInstruction(64, false, 10, 33, c4, 22, 9)
|
||||
assert.Equal(t, 6, l4)
|
||||
assert.Equal(t, FT_SKIP, t4)
|
||||
assert.Equal(t, c4[0], r4[0])
|
||||
assert.Equal(t, c4[1], r4[1])
|
||||
assert.Equal(t, c4[2], r4[2])
|
||||
assert.Equal(t, c4[3], r4[3])
|
||||
assert.Equal(t, c4[4], r4[4])
|
||||
assert.Equal(t, c4[5], r4[5])
|
||||
|
||||
// jump from outside patching erea to within patching erea
|
||||
c5 := []byte{0x0f, 0x85, 0xce, 0xff, 0xff, 0xff} // jne -50
|
||||
|
||||
l5, t5, r5 := FixOneInstruction(64, false, 10, 60, c5, 1000, 9)
|
||||
assert.Equal(t, 6, l5)
|
||||
assert.Equal(t, FT_CondJmp, t5)
|
||||
assert.Equal(t, c5[0], r5[0])
|
||||
assert.Equal(t, c5[1], r5[1])
|
||||
|
||||
assert.Equal(t, int32(940), byteToInt32(r5[2:]))
|
||||
|
||||
// jump within patching erea
|
||||
c6 := []byte{0x0f, 0x85, 0x10, 0x00, 0x00, 0x00} // jne 16
|
||||
|
||||
l6, t6, r6 := FixOneInstruction(64, false, 10, 12, c6, 1000, 30)
|
||||
assert.Equal(t, 6, l6)
|
||||
assert.Equal(t, FT_SKIP, t6)
|
||||
assert.Equal(t, c6[0], r6[0])
|
||||
assert.Equal(t, c6[1], r6[1])
|
||||
assert.Equal(t, c6[2], r6[2])
|
||||
assert.Equal(t, c6[3], r6[3])
|
||||
assert.Equal(t, c6[4], r6[4])
|
||||
assert.Equal(t, c6[5], r6[5])
|
||||
}
|
||||
|
||||
func TestFixOneInstructionForFixByteJmp(t *testing.T) {
|
||||
// jump from within patching erea to outside, negative fix
|
||||
c1 := []byte{0xe9, 0x10, 0x00, 0x00, 0x00} // jmp 16
|
||||
|
||||
l1, t1, r1 := FixOneInstruction(64, false, 20, 22, c1, 100, 8)
|
||||
assert.Equal(t, 5, l1)
|
||||
assert.Equal(t, FT_JMP, t1)
|
||||
assert.Equal(t, c1[0], r1[0])
|
||||
assert.Equal(t, int32(-64), byteToInt32(r1[1:]))
|
||||
|
||||
// jump from within patching erea to outside, positive fix
|
||||
c2 := []byte{0xe9, 0x40, 0x00, 0x00, 0x00} // jmp 64
|
||||
|
||||
l2, t2, r2 := FixOneInstruction(64, false, 2, 4, c2, 32, 9)
|
||||
assert.Equal(t, 5, l2)
|
||||
assert.Equal(t, FT_JMP, t2)
|
||||
assert.Equal(t, c2[0], r2[0])
|
||||
assert.Equal(t, int32(34), byteToInt32(r2[1:]))
|
||||
|
||||
// overflow test
|
||||
c3 := []byte{0xe9, 0xfe, 0xff, 0xff, 0x7f} // jmp 64
|
||||
|
||||
l3, t3, r3 := FixOneInstruction(64, false, 10000, 10004, c3, 100, 16)
|
||||
assert.Equal(t, 5, l3)
|
||||
assert.Equal(t, FT_OVERFLOW, t3)
|
||||
assert.Equal(t, c3[0], r3[0])
|
||||
assert.Equal(t, c3[1], r3[1])
|
||||
assert.Equal(t, c3[2], r3[2])
|
||||
assert.Equal(t, c3[3], r3[3])
|
||||
assert.Equal(t, c3[4], r3[4])
|
||||
|
||||
// jump from outside patching erea to outside of patching erea
|
||||
c4 := []byte{0xe9, 0x40, 0x00, 0x00, 0x00} // jmp 64
|
||||
|
||||
l4, t4, r4 := FixOneInstruction(64, false, 10, 33, c4, 22, 9)
|
||||
assert.Equal(t, 5, l4)
|
||||
assert.Equal(t, FT_SKIP, t4)
|
||||
assert.Equal(t, c4[0], r4[0])
|
||||
assert.Equal(t, c4[1], r4[1])
|
||||
assert.Equal(t, c4[2], r4[2])
|
||||
assert.Equal(t, c4[3], r4[3])
|
||||
assert.Equal(t, c4[4], r4[4])
|
||||
|
||||
// jump from outside patching erea to within patching erea
|
||||
c5 := []byte{0xe9, 0xce, 0xff, 0xff, 0xff} // jmp -50
|
||||
|
||||
l5, t5, r5 := FixOneInstruction(64, false, 10, 60, c5, 1000, 9)
|
||||
assert.Equal(t, 5, l5)
|
||||
assert.Equal(t, FT_JMP, t5)
|
||||
assert.Equal(t, c5[0], r5[0])
|
||||
assert.Equal(t, int32(940), byteToInt32(r5[1:]))
|
||||
|
||||
// jump within patching erea
|
||||
c6 := []byte{0xe9, 0x10, 0x00, 0x00, 0x00} // jmp 16
|
||||
|
||||
l6, t6, r6 := FixOneInstruction(64, false, 10, 12, c6, 1000, 30)
|
||||
assert.Equal(t, 5, l6)
|
||||
assert.Equal(t, FT_SKIP, t6)
|
||||
assert.Equal(t, c6[0], r6[0])
|
||||
assert.Equal(t, c6[1], r6[1])
|
||||
assert.Equal(t, c6[2], r6[2])
|
||||
assert.Equal(t, c6[3], r6[3])
|
||||
assert.Equal(t, c6[4], r6[4])
|
||||
|
||||
// jump from outside to outside, sign test
|
||||
c7 := []byte{0xe8, 0xdc, 0xfb, 0xff, 0xff} // jmp -1060
|
||||
l7, t7, r7 := FixOneInstruction(64, false, 2000, 4100, c7, 10000, 30)
|
||||
assert.Equal(t, 5, l7)
|
||||
assert.Equal(t, FT_SKIP, t7)
|
||||
assert.Equal(t, c7[0], r7[0])
|
||||
assert.Equal(t, c7[1], r7[1])
|
||||
assert.Equal(t, c7[2], r7[2])
|
||||
assert.Equal(t, c7[3], r7[3])
|
||||
assert.Equal(t, c7[4], r7[4])
|
||||
}
|
||||
|
||||
func TestFixFuncCode(t *testing.T) {
|
||||
p := []byte{0x64, 0x48, 0x8b, 0x0c, 0x25, 0xf8, 0xff, 0xff, 0xff} // move %fs:0xfffffffffffffff8, %rcx
|
||||
c1 := []byte{
|
||||
/*0:*/ 0x64, 0x48, 0x8b, 0x0c, 0x25, 0xf8, 0xff, 0xff, 0xff, // move %fs:0xfffffffffffffff8, %rcx sz:9
|
||||
/*9:*/ 0x48, 0x8d, 0x44, 0x24, 0xe0, // lea -0x20(%rsp),%rax sz:5
|
||||
/*14:*/ 0x48, 0x3b, 0x41, 0x10, // cmp 0x10(%rcx),%rax sz:4
|
||||
/*18:*/ 0x0f, 0x86, 0xc3, 0x01, 0x00, 0x00, // jbe 451 sz:6
|
||||
/*24:*/ 0x48, 0x81, 0xec, 0xa0, 0x00, 0x00, 0x00, // sub $0xa0,%rsp sz:7
|
||||
/*31:*/ 0x48, 0x8b, 0x9c, 0x24, 0xa8, 0x00, 0x00, 0x00, // mov 0xa8(%rsp),%rbx sz:8
|
||||
/*39:*/ 0xe3, 0x02, // jmp 02 sz:2
|
||||
/*41:*/ 0x90, // nop sz:1
|
||||
/*42:*/ 0x90, // nop sz:1
|
||||
/*43:*/ 0x90, // nop sz:1
|
||||
/*44:*/ 0x90, // nop sz:1
|
||||
//////////patching erea end: 45 bytes/////////////////////////////////////////
|
||||
/*45:*/ 0x48, 0x89, 0x5c, 0x24, 0x40, // mov %rbx,0x40(%rsp) sz:5
|
||||
/*50:*/ 0xe9, 0xd2, 0xff, 0xff, 0xff, // jmp -46 sz:5
|
||||
/*55:*/ 0x90, // nop sz:1
|
||||
/*56:*/ 0x90, // nop sz:1
|
||||
/*57:*/ 0x90, // nop sz:1
|
||||
/*58:*/ 0x90, // nop sz:1
|
||||
}
|
||||
|
||||
SetFuncPrologue(64, []byte{0x64, 0x48, 0x8b, 0x0c, 0x25, 0xf8, 0xff, 0xff, 0xff, 0x48})
|
||||
sh1 := (*reflect.SliceHeader)((unsafe.Pointer(&c1)))
|
||||
|
||||
move_sz := 45
|
||||
startAddr := sh1.Data
|
||||
toAddr := startAddr + 100000
|
||||
|
||||
fix1, err1 := FixTargetFuncCode(64, startAddr, uint32(len(c1)), toAddr, move_sz)
|
||||
|
||||
assert.Nil(t, err1)
|
||||
assert.Equal(t, 2, len(fix1))
|
||||
|
||||
assert.Equal(t, startAddr+uintptr(18), fix1[0].Addr)
|
||||
assert.Equal(t, startAddr+uintptr(50), fix1[1].Addr)
|
||||
|
||||
assert.Equal(t, 6, len(fix1[0].Code))
|
||||
assert.Equal(t, byte(0x0f), fix1[0].Code[0])
|
||||
assert.Equal(t, byte(0x86), fix1[0].Code[1])
|
||||
assert.Equal(t, int32(startAddr+451-toAddr), byteToInt32(fix1[0].Code[2:]))
|
||||
|
||||
assert.Equal(t, 5, len(fix1[1].Code))
|
||||
assert.Equal(t, byte(0xe9), fix1[1].Code[0])
|
||||
assert.Equal(t, int32(toAddr+9-startAddr-50-5), byteToInt32(fix1[1].Code[1:]))
|
||||
|
||||
c2 := append(c1, p...)
|
||||
sh2 := (*reflect.SliceHeader)((unsafe.Pointer(&c2)))
|
||||
startAddr = sh2.Data
|
||||
toAddr = startAddr + 100000
|
||||
|
||||
fix2, err2 := FixTargetFuncCode(64, startAddr, 0, toAddr, move_sz)
|
||||
|
||||
assert.Nil(t, err2)
|
||||
assert.Equal(t, 2, len(fix2))
|
||||
|
||||
assert.Equal(t, startAddr+uintptr(18), fix2[0].Addr)
|
||||
assert.Equal(t, startAddr+uintptr(50), fix2[1].Addr)
|
||||
|
||||
assert.Equal(t, 6, len(fix2[0].Code))
|
||||
assert.Equal(t, byte(0x0f), fix2[0].Code[0])
|
||||
assert.Equal(t, byte(0x86), fix2[0].Code[1])
|
||||
assert.Equal(t, int32(startAddr+451-toAddr), byteToInt32(fix2[0].Code[2:]))
|
||||
|
||||
assert.Equal(t, 5, len(fix2[1].Code))
|
||||
assert.Equal(t, byte(0xe9), fix2[1].Code[0])
|
||||
assert.Equal(t, int32(toAddr+9-startAddr-50-5), byteToInt32(fix2[1].Code[1:]))
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
package gohook
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
//go:noinline
|
||||
func foo_short_call(a int) (int, error) {
|
||||
//fmt.Printf("calling short call origin func\n")
|
||||
return 3 + foo_short_call2(a), nil
|
||||
}
|
||||
|
||||
//go:noinline
|
||||
func foo_short_call2(a int) int {
|
||||
fmt.Printf("in short call2\n")
|
||||
return 3 + a
|
||||
}
|
||||
|
||||
//go:noinline
|
||||
func foo_short_call_replace(a int) (int, error) {
|
||||
fmt.Printf("calling short call replace func\n")
|
||||
r, _ := foo_short_call_trampoline(a)
|
||||
return a + 1000 + r, nil
|
||||
}
|
||||
|
||||
func dummy_delimiter(id string) string {
|
||||
for {
|
||||
fmt.Printf("calling victim trampoline")
|
||||
if id == "miliao" {
|
||||
return "done"
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
ret := "miliao"
|
||||
ret += foo_for_inplace_fix("test")
|
||||
ret += foo_for_inplace_fix("test")
|
||||
ret += foo_for_inplace_fix("test")
|
||||
ret += foo_for_inplace_fix("test")
|
||||
|
||||
fmt.Printf("len1:%d\n", len(id))
|
||||
fmt.Printf("len2:%d\n", len(ret))
|
||||
|
||||
ret += foo_for_inplace_fix_delimiter(id)
|
||||
|
||||
return id + ret
|
||||
}
|
||||
|
||||
//go:noinline
|
||||
func foo_short_call_trampoline(a int) (int, error) {
|
||||
for {
|
||||
fmt.Printf("printing a:%d\n", a)
|
||||
a++
|
||||
if a > 233 {
|
||||
fmt.Printf("done printing a:%d\n", a)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
dummy_delimiter("miliao")
|
||||
|
||||
return a + 233, nil
|
||||
}
|
||||
|
||||
func TestShortCall(t *testing.T) {
|
||||
r, _ := foo_short_call(32)
|
||||
assert.Equal(t, 38, r)
|
||||
|
||||
addr := GetFuncAddr(foo_short_call)
|
||||
sz1 := GetFuncInstSize(foo_short_call)
|
||||
addr2 := addr + uintptr(sz1)
|
||||
fmt.Printf("start hook real short call func, start:%x, end:%x\n", addr, addr2)
|
||||
|
||||
err := Hook(foo_short_call, foo_short_call_replace, foo_short_call_trampoline)
|
||||
assert.Nil(t, err)
|
||||
|
||||
r1, _ := foo_short_call(22)
|
||||
assert.Equal(t, 1050, r1)
|
||||
|
||||
UnHook(foo_short_call)
|
||||
|
||||
r2, _ := foo_short_call(32)
|
||||
assert.Equal(t, 38, r2)
|
||||
|
||||
code := make([]byte, 0, sz1)
|
||||
for i := 0; i < int(sz1); i++ {
|
||||
code = append(code, 0x90)
|
||||
}
|
||||
|
||||
code1 := []byte{0xeb, 0x4}
|
||||
code2 := []byte{0xeb, 0x5}
|
||||
|
||||
copy(code, code1)
|
||||
copy(code[2:], code2)
|
||||
|
||||
ret := sz1 - 5
|
||||
jmp1 := sz1 - 4
|
||||
jmp2 := sz1 - 2
|
||||
|
||||
if sz1 > 0x7f {
|
||||
ret = 0x70 - 5
|
||||
jmp1 = 0x70 - 4
|
||||
jmp2 = 0x70 - 2
|
||||
}
|
||||
|
||||
code[ret] = byte(0xc3)
|
||||
|
||||
code3 := []byte{0xeb, byte(-jmp1 - 2)}
|
||||
code4 := []byte{0xeb, byte(-jmp2 - 2)}
|
||||
|
||||
copy(code[jmp1:], code3)
|
||||
copy(code[jmp2:], code4)
|
||||
|
||||
assert.Equal(t, code[:4], append(code1, code2...))
|
||||
|
||||
CopyInstruction(addr, code)
|
||||
|
||||
err = Hook(foo_short_call, foo_short_call_replace, foo_short_call_trampoline)
|
||||
assert.Nil(t, err)
|
||||
|
||||
fmt.Printf("fix code for foo_short_call:\n%s\n", ShowDebugInfo())
|
||||
|
||||
foo_short_call(22)
|
||||
|
||||
addr3 := addr2 + uintptr(2)
|
||||
fc := runtime.FuncForPC(addr3)
|
||||
|
||||
assert.NotNil(t, fc)
|
||||
|
||||
fmt.Printf("func name get from addr beyond scope:%s\n", fc.Name())
|
||||
assert.Equal(t, addr, fc.Entry())
|
||||
|
||||
f, l := fc.FileLine(addr2 + uintptr(3))
|
||||
assert.Equal(t, 0, l)
|
||||
assert.Equal(t, "?", f)
|
||||
fmt.Printf("file:%s, line:%d\n", f, l)
|
||||
}
|
|
@ -0,0 +1,237 @@
|
|||
package gohook
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func dummy(v int) string {
|
||||
return fmt.Sprintf("some text:%d", v)
|
||||
}
|
||||
|
||||
type CodeInfo struct {
|
||||
How string
|
||||
Origin []byte
|
||||
Fix []CodeFix
|
||||
TrampolineOrig []byte
|
||||
}
|
||||
|
||||
func makeSliceFromPointer(p uintptr, length int) []byte {
|
||||
return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
|
||||
Data: p,
|
||||
Len: length,
|
||||
Cap: length,
|
||||
}))
|
||||
}
|
||||
|
||||
func GetFuncInstSize(f interface{}) uint32 {
|
||||
sz := uint32(0)
|
||||
ptr := reflect.ValueOf(f).Pointer()
|
||||
if elfInfo != nil {
|
||||
sz, _ = elfInfo.GetFuncSize(ptr)
|
||||
}
|
||||
|
||||
if sz == 0 {
|
||||
sz, _ = GetFuncSizeByGuess(GetArchMode(), ptr, true)
|
||||
}
|
||||
|
||||
return sz
|
||||
}
|
||||
|
||||
func CopyFunction(allowCall bool, from, to interface{}, info *CodeInfo) ([]byte, error) {
|
||||
s := reflect.ValueOf(from).Pointer()
|
||||
d := reflect.ValueOf(to).Pointer()
|
||||
|
||||
mode := GetArchMode()
|
||||
sz1 := getFuncSize(mode, s, true)
|
||||
sz2 := getFuncSize(mode, d, true)
|
||||
return doCopyFunction(mode, allowCall, s, d, sz1, sz2, info)
|
||||
}
|
||||
|
||||
func getFuncSize(mode int, addr uintptr, minimal bool) uint32 {
|
||||
sz := uint32(0)
|
||||
if elfInfo != nil {
|
||||
sz, _ = elfInfo.GetFuncSize(addr)
|
||||
}
|
||||
|
||||
var err error
|
||||
if sz == 0 {
|
||||
sz, err = GetFuncSizeByGuess(mode, addr, minimal)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
return sz
|
||||
}
|
||||
|
||||
func doFixFuncInplace(mode int, addr, to uintptr, funcSz, to_sz int, info *CodeInfo, jumpSize int) error {
|
||||
fix, err := fixFuncInstructionInplace(mode, addr, to, funcSz, to_sz, jumpSize)
|
||||
for retry := 0; err != nil && retry < 8 && to_sz < funcSz/2; retry++ {
|
||||
if err != errInplaceFixSizeNotEnough {
|
||||
break
|
||||
}
|
||||
|
||||
to_sz += 16
|
||||
f := makeSliceFromPointer(addr, to_sz+16)
|
||||
to_sz = GetInsLenGreaterThan(mode, f, to_sz)
|
||||
|
||||
// fmt.Printf("retry fix inplace by extending move size, move sz:%d\n", to_sz)
|
||||
|
||||
fix, err = fixFuncInstructionInplace(mode, addr, to, funcSz, to_sz, jumpSize)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
size2 := 0
|
||||
total_len := 0
|
||||
for _, f := range fix {
|
||||
total_len += len(f.Code)
|
||||
if f.Foreign {
|
||||
// fmt.Printf("foreign code:%x, sz:%d\n", f.Addr, len(f.Code))
|
||||
size2 += len(f.Code)
|
||||
}
|
||||
}
|
||||
|
||||
//jump from trampoline back to origin func
|
||||
jumpcode2 := genJumpCode(mode, false, addr+uintptr(jumpSize), to+uintptr(size2))
|
||||
|
||||
origin := makeSliceFromPointer(addr, int(total_len))
|
||||
sf := make([]byte, total_len)
|
||||
copy(sf, origin)
|
||||
|
||||
tramp_origin := makeSliceFromPointer(to, size2+64)
|
||||
tf := make([]byte, len(tramp_origin))
|
||||
copy(tf, tramp_origin)
|
||||
|
||||
for _, f := range fix {
|
||||
CopyInstruction(f.Addr, f.Code)
|
||||
}
|
||||
|
||||
CopyInstruction(to+uintptr(size2), jumpcode2)
|
||||
|
||||
info.Fix = fix
|
||||
info.Origin = sf
|
||||
info.TrampolineOrig = tf
|
||||
return nil
|
||||
}
|
||||
|
||||
func doCopyFunction(mode int, allowCall bool, from, to uintptr, sz1, sz2 uint32, info *CodeInfo) ([]byte, error) {
|
||||
if sz1 > sz2+1 { // add trailing int3 to the end
|
||||
return nil, fmt.Errorf("source addr:%x, target addr:%x, sizeof source func(%d) > sizeof of target func(%d)", from, to, sz1, sz2)
|
||||
}
|
||||
|
||||
fix, err2 := copyFuncInstruction(mode, from, to, int(sz1), allowCall)
|
||||
if err2 != nil {
|
||||
return nil, err2
|
||||
}
|
||||
|
||||
origin := makeSliceFromPointer(to, int(sz2))
|
||||
sf := make([]byte, sz2)
|
||||
copy(sf, origin)
|
||||
|
||||
curAddr := to
|
||||
for _, f := range fix {
|
||||
CopyInstruction(curAddr, f.Code)
|
||||
f.Addr = curAddr
|
||||
curAddr += uintptr(len(f.Code))
|
||||
}
|
||||
|
||||
info.Fix = fix
|
||||
return sf, nil
|
||||
}
|
||||
|
||||
func hookFunction(mode int, rdxIndirect bool, target, replace, trampoline uintptr) (*CodeInfo, error) {
|
||||
info := &CodeInfo{}
|
||||
jumpcode := genJumpCode(mode, rdxIndirect, replace, target)
|
||||
|
||||
insLen := len(jumpcode)
|
||||
if trampoline != uintptr(0) {
|
||||
f := makeSliceFromPointer(target, len(jumpcode)+65)
|
||||
insLen = GetInsLenGreaterThan(mode, f, len(jumpcode))
|
||||
}
|
||||
|
||||
// target slice
|
||||
ts := makeSliceFromPointer(target, insLen)
|
||||
info.Origin = make([]byte, len(ts))
|
||||
copy(info.Origin, ts)
|
||||
|
||||
info.How = "jump"
|
||||
target_body_off := insLen
|
||||
|
||||
if trampoline != uintptr(0) {
|
||||
sz := uint32(0)
|
||||
if elfInfo != nil {
|
||||
sz, _ = elfInfo.GetFuncSize(target)
|
||||
}
|
||||
|
||||
fix, err := FixTargetFuncCode(mode, target, sz, trampoline, insLen)
|
||||
|
||||
if err != nil {
|
||||
sz1 := getFuncSize(mode, target, false)
|
||||
sz2 := getFuncSize(mode, trampoline, false)
|
||||
if sz1 <= 0 || sz2 <= 0 {
|
||||
return nil, fmt.Errorf("failed calc func size")
|
||||
}
|
||||
|
||||
err1 := doFixFuncInplace(mode, target, trampoline, int(sz1), insLen, info, len(jumpcode))
|
||||
if err1 != nil {
|
||||
info.How = "copy"
|
||||
origin, err2 := doCopyFunction(mode, false, target, trampoline, sz1, sz2, info)
|
||||
if err2 != nil {
|
||||
return nil, fmt.Errorf("both fix/fix2/copy failed, fix:%s, fix2:%s, copy:%s", err.Error(), err1.Error(), err2.Error())
|
||||
}
|
||||
info.TrampolineOrig = origin
|
||||
} else {
|
||||
info.How = "adjust"
|
||||
/*
|
||||
ts = makeSliceFromPointer(target, len(jumpcode)+65)
|
||||
insLen = GetInsLenGreaterThan(mode, ts, len(jumpcode))
|
||||
ts = makeSliceFromPointer(target, insLen)
|
||||
*/
|
||||
}
|
||||
} else {
|
||||
info.How = "fix"
|
||||
for _, v := range fix {
|
||||
origin := makeSliceFromPointer(v.Addr, len(v.Code))
|
||||
f := make([]byte, len(v.Code))
|
||||
copy(f, origin)
|
||||
CopyInstruction(v.Addr, v.Code)
|
||||
v.Code = f
|
||||
info.Fix = append(info.Fix, v)
|
||||
}
|
||||
|
||||
jumpcode2 := genJumpCode(mode, false, target+uintptr(target_body_off), trampoline+uintptr(insLen))
|
||||
f2 := makeSliceFromPointer(trampoline, insLen+len(jumpcode2)*2)
|
||||
insLen2 := GetInsLenGreaterThan(mode, f2, insLen+len(jumpcode2))
|
||||
info.TrampolineOrig = make([]byte, insLen2)
|
||||
ts2 := makeSliceFromPointer(trampoline, insLen2)
|
||||
copy(info.TrampolineOrig, ts2)
|
||||
CopyInstruction(trampoline, ts)
|
||||
CopyInstruction(trampoline+uintptr(insLen), jumpcode2)
|
||||
}
|
||||
}
|
||||
|
||||
CopyInstruction(target, jumpcode)
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func printInstructionFix(v CodeFix, origin []byte) {
|
||||
for _, c := range v.Code {
|
||||
fmt.Printf(" %x", c)
|
||||
}
|
||||
|
||||
fmt.Printf(", origin:")
|
||||
for _, c := range origin {
|
||||
fmt.Printf(" %x", c)
|
||||
}
|
||||
fmt.Printf("\n")
|
||||
}
|
||||
|
||||
func GetFuncAddr(f interface{}) uintptr {
|
||||
fv := reflect.ValueOf(f)
|
||||
return fv.Pointer()
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
// +build darwin
|
||||
package gohook
|
||||
|
||||
var (
|
||||
defaultFuncPrologue64 = []byte{0x65, 0x48, 0x8b, 0x0c, 0x25, 0x30, 0x00, 0x00, 0x00, 0x48}
|
||||
defaultFuncPrologue32 = []byte{0x65, 0x8b, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x8b, 0x89, 0xfc, 0xff, 0xff, 0xff}
|
||||
)
|
|
@ -0,0 +1,8 @@
|
|||
//+build linux
|
||||
|
||||
package gohook
|
||||
|
||||
var (
|
||||
defaultFuncPrologue64 = []byte{0x64, 0x48, 0x8b, 0x0c, 0x25, 0xf8, 0xff, 0xff, 0xff, 0x48}
|
||||
defaultFuncPrologue32 = []byte{0x65, 0x8b, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x8b, 0x89, 0xfc, 0xff, 0xff, 0xff}
|
||||
)
|
|
@ -0,0 +1,32 @@
|
|||
// +build !windows
|
||||
|
||||
package gohook
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func getPageAddr(ptr uintptr) uintptr {
|
||||
return ptr & ^(uintptr(syscall.Getpagesize() - 1))
|
||||
}
|
||||
|
||||
func setPageWritable(addr uintptr, length int, prot int) {
|
||||
pageSize := syscall.Getpagesize()
|
||||
for p := getPageAddr(addr); p < addr+uintptr(length); p += uintptr(pageSize) {
|
||||
page := makeSliceFromPointer(p, pageSize)
|
||||
err := syscall.Mprotect(page, prot)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func CopyInstruction(location uintptr, data []byte) {
|
||||
f := makeSliceFromPointer(location, len(data))
|
||||
setPageWritable(location, len(data), syscall.PROT_READ|syscall.PROT_WRITE|syscall.PROT_EXEC)
|
||||
sz := copy(f, data[:])
|
||||
setPageWritable(location, len(data), syscall.PROT_READ|syscall.PROT_EXEC)
|
||||
if sz != len(data) {
|
||||
panic("copy instruction to target failed")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package gohook
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultFuncPrologue32 = []byte{0x65, 0x8b, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x8b, 0x89, 0xfc, 0xff, 0xff, 0xff}
|
||||
defaultFuncPrologue64 = []byte{0x65, 0x48, 0x8B, 0x0C, 0x25, 0x28, 0x00, 0x00, 0x00}
|
||||
)
|
||||
|
||||
const PAGE_EXECUTE_READWRITE = 0x40
|
||||
|
||||
var procVirtualProtect = syscall.NewLazyDLL("kernel32.dll").NewProc("VirtualProtect")
|
||||
|
||||
func virtualProtect(lpAddress uintptr, dwSize int, flNewProtect uint32, lpflOldProtect unsafe.Pointer) error {
|
||||
ret, _, _ := procVirtualProtect.Call(
|
||||
lpAddress,
|
||||
uintptr(dwSize),
|
||||
uintptr(flNewProtect),
|
||||
uintptr(lpflOldProtect))
|
||||
if ret == 0 {
|
||||
return syscall.GetLastError()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func CopyInstruction(location uintptr, data []byte) {
|
||||
f := makeSliceFromPointer(location, len(data))
|
||||
|
||||
var oldPerms uint32
|
||||
err := virtualProtect(location, len(data), PAGE_EXECUTE_READWRITE, unsafe.Pointer(&oldPerms))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
copy(f, data[:])
|
||||
|
||||
var tmp uint32
|
||||
err = virtualProtect(location, len(data), oldPerms, unsafe.Pointer(&tmp))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
Copyright 2019 The logr Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package testing
|
||||
|
||||
import "github.com/go-logr/logr"
|
||||
|
||||
// NullLogger is a logr.Logger that does nothing.
|
||||
type NullLogger struct{}
|
||||
|
||||
var _ logr.Logger = NullLogger{}
|
||||
|
||||
func (_ NullLogger) Info(_ string, _ ...interface{}) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
func (_ NullLogger) Enabled() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (_ NullLogger) Error(_ error, _ string, _ ...interface{}) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
func (log NullLogger) V(_ int) logr.Logger {
|
||||
return log
|
||||
}
|
||||
|
||||
func (log NullLogger) WithName(_ string) logr.Logger {
|
||||
return log
|
||||
}
|
||||
|
||||
func (log NullLogger) WithValues(_ ...interface{}) logr.Logger {
|
||||
return log
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
Copyright 2019 The logr Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package testing
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
)
|
||||
|
||||
// TestLogger is a logr.Logger that prints through a testing.T object.
|
||||
// Only error logs will have any effect.
|
||||
type TestLogger struct {
|
||||
T *testing.T
|
||||
}
|
||||
|
||||
var _ logr.Logger = TestLogger{}
|
||||
|
||||
func (_ TestLogger) Info(_ string, _ ...interface{}) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
func (_ TestLogger) Enabled() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (log TestLogger) Error(err error, msg string, args ...interface{}) {
|
||||
log.T.Logf("%s: %v -- %v", msg, err, args)
|
||||
}
|
||||
|
||||
func (log TestLogger) V(v int) logr.Logger {
|
||||
return log
|
||||
}
|
||||
|
||||
func (log TestLogger) WithName(_ string) logr.Logger {
|
||||
return log
|
||||
}
|
||||
|
||||
func (log TestLogger) WithValues(_ ...interface{}) logr.Logger {
|
||||
return log
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
Copyright (c) 2013, Patrick Mezard
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
The names of its contributors may not be used to endorse or promote
|
||||
products derived from this software without specific prior written
|
||||
permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,772 @@
|
|||
// Package difflib is a partial port of Python difflib module.
|
||||
//
|
||||
// It provides tools to compare sequences of strings and generate textual diffs.
|
||||
//
|
||||
// The following class and functions have been ported:
|
||||
//
|
||||
// - SequenceMatcher
|
||||
//
|
||||
// - unified_diff
|
||||
//
|
||||
// - context_diff
|
||||
//
|
||||
// Getting unified diffs was the main goal of the port. Keep in mind this code
|
||||
// is mostly suitable to output text differences in a human friendly way, there
|
||||
// are no guarantees generated diffs are consumable by patch(1).
|
||||
package difflib
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func calculateRatio(matches, length int) float64 {
|
||||
if length > 0 {
|
||||
return 2.0 * float64(matches) / float64(length)
|
||||
}
|
||||
return 1.0
|
||||
}
|
||||
|
||||
type Match struct {
|
||||
A int
|
||||
B int
|
||||
Size int
|
||||
}
|
||||
|
||||
type OpCode struct {
|
||||
Tag byte
|
||||
I1 int
|
||||
I2 int
|
||||
J1 int
|
||||
J2 int
|
||||
}
|
||||
|
||||
// SequenceMatcher compares sequence of strings. The basic
|
||||
// algorithm predates, and is a little fancier than, an algorithm
|
||||
// published in the late 1980's by Ratcliff and Obershelp under the
|
||||
// hyperbolic name "gestalt pattern matching". The basic idea is to find
|
||||
// the longest contiguous matching subsequence that contains no "junk"
|
||||
// elements (R-O doesn't address junk). The same idea is then applied
|
||||
// recursively to the pieces of the sequences to the left and to the right
|
||||
// of the matching subsequence. This does not yield minimal edit
|
||||
// sequences, but does tend to yield matches that "look right" to people.
|
||||
//
|
||||
// SequenceMatcher tries to compute a "human-friendly diff" between two
|
||||
// sequences. Unlike e.g. UNIX(tm) diff, the fundamental notion is the
|
||||
// longest *contiguous* & junk-free matching subsequence. That's what
|
||||
// catches peoples' eyes. The Windows(tm) windiff has another interesting
|
||||
// notion, pairing up elements that appear uniquely in each sequence.
|
||||
// That, and the method here, appear to yield more intuitive difference
|
||||
// reports than does diff. This method appears to be the least vulnerable
|
||||
// to synching up on blocks of "junk lines", though (like blank lines in
|
||||
// ordinary text files, or maybe "<P>" lines in HTML files). That may be
|
||||
// because this is the only method of the 3 that has a *concept* of
|
||||
// "junk" <wink>.
|
||||
//
|
||||
// Timing: Basic R-O is cubic time worst case and quadratic time expected
|
||||
// case. SequenceMatcher is quadratic time for the worst case and has
|
||||
// expected-case behavior dependent in a complicated way on how many
|
||||
// elements the sequences have in common; best case time is linear.
|
||||
type SequenceMatcher struct {
|
||||
a []string
|
||||
b []string
|
||||
b2j map[string][]int
|
||||
IsJunk func(string) bool
|
||||
autoJunk bool
|
||||
bJunk map[string]struct{}
|
||||
matchingBlocks []Match
|
||||
fullBCount map[string]int
|
||||
bPopular map[string]struct{}
|
||||
opCodes []OpCode
|
||||
}
|
||||
|
||||
func NewMatcher(a, b []string) *SequenceMatcher {
|
||||
m := SequenceMatcher{autoJunk: true}
|
||||
m.SetSeqs(a, b)
|
||||
return &m
|
||||
}
|
||||
|
||||
func NewMatcherWithJunk(a, b []string, autoJunk bool,
|
||||
isJunk func(string) bool) *SequenceMatcher {
|
||||
|
||||
m := SequenceMatcher{IsJunk: isJunk, autoJunk: autoJunk}
|
||||
m.SetSeqs(a, b)
|
||||
return &m
|
||||
}
|
||||
|
||||
// Set two sequences to be compared.
|
||||
func (m *SequenceMatcher) SetSeqs(a, b []string) {
|
||||
m.SetSeq1(a)
|
||||
m.SetSeq2(b)
|
||||
}
|
||||
|
||||
// Set the first sequence to be compared. The second sequence to be compared is
|
||||
// not changed.
|
||||
//
|
||||
// SequenceMatcher computes and caches detailed information about the second
|
||||
// sequence, so if you want to compare one sequence S against many sequences,
|
||||
// use .SetSeq2(s) once and call .SetSeq1(x) repeatedly for each of the other
|
||||
// sequences.
|
||||
//
|
||||
// See also SetSeqs() and SetSeq2().
|
||||
func (m *SequenceMatcher) SetSeq1(a []string) {
|
||||
if &a == &m.a {
|
||||
return
|
||||
}
|
||||
m.a = a
|
||||
m.matchingBlocks = nil
|
||||
m.opCodes = nil
|
||||
}
|
||||
|
||||
// Set the second sequence to be compared. The first sequence to be compared is
|
||||
// not changed.
|
||||
func (m *SequenceMatcher) SetSeq2(b []string) {
|
||||
if &b == &m.b {
|
||||
return
|
||||
}
|
||||
m.b = b
|
||||
m.matchingBlocks = nil
|
||||
m.opCodes = nil
|
||||
m.fullBCount = nil
|
||||
m.chainB()
|
||||
}
|
||||
|
||||
func (m *SequenceMatcher) chainB() {
|
||||
// Populate line -> index mapping
|
||||
b2j := map[string][]int{}
|
||||
for i, s := range m.b {
|
||||
indices := b2j[s]
|
||||
indices = append(indices, i)
|
||||
b2j[s] = indices
|
||||
}
|
||||
|
||||
// Purge junk elements
|
||||
m.bJunk = map[string]struct{}{}
|
||||
if m.IsJunk != nil {
|
||||
junk := m.bJunk
|
||||
for s, _ := range b2j {
|
||||
if m.IsJunk(s) {
|
||||
junk[s] = struct{}{}
|
||||
}
|
||||
}
|
||||
for s, _ := range junk {
|
||||
delete(b2j, s)
|
||||
}
|
||||
}
|
||||
|
||||
// Purge remaining popular elements
|
||||
popular := map[string]struct{}{}
|
||||
n := len(m.b)
|
||||
if m.autoJunk && n >= 200 {
|
||||
ntest := n/100 + 1
|
||||
for s, indices := range b2j {
|
||||
if len(indices) > ntest {
|
||||
popular[s] = struct{}{}
|
||||
}
|
||||
}
|
||||
for s, _ := range popular {
|
||||
delete(b2j, s)
|
||||
}
|
||||
}
|
||||
m.bPopular = popular
|
||||
m.b2j = b2j
|
||||
}
|
||||
|
||||
func (m *SequenceMatcher) isBJunk(s string) bool {
|
||||
_, ok := m.bJunk[s]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Find longest matching block in a[alo:ahi] and b[blo:bhi].
|
||||
//
|
||||
// If IsJunk is not defined:
|
||||
//
|
||||
// Return (i,j,k) such that a[i:i+k] is equal to b[j:j+k], where
|
||||
// alo <= i <= i+k <= ahi
|
||||
// blo <= j <= j+k <= bhi
|
||||
// and for all (i',j',k') meeting those conditions,
|
||||
// k >= k'
|
||||
// i <= i'
|
||||
// and if i == i', j <= j'
|
||||
//
|
||||
// In other words, of all maximal matching blocks, return one that
|
||||
// starts earliest in a, and of all those maximal matching blocks that
|
||||
// start earliest in a, return the one that starts earliest in b.
|
||||
//
|
||||
// If IsJunk is defined, first the longest matching block is
|
||||
// determined as above, but with the additional restriction that no
|
||||
// junk element appears in the block. Then that block is extended as
|
||||
// far as possible by matching (only) junk elements on both sides. So
|
||||
// the resulting block never matches on junk except as identical junk
|
||||
// happens to be adjacent to an "interesting" match.
|
||||
//
|
||||
// If no blocks match, return (alo, blo, 0).
|
||||
func (m *SequenceMatcher) findLongestMatch(alo, ahi, blo, bhi int) Match {
|
||||
// CAUTION: stripping common prefix or suffix would be incorrect.
|
||||
// E.g.,
|
||||
// ab
|
||||
// acab
|
||||
// Longest matching block is "ab", but if common prefix is
|
||||
// stripped, it's "a" (tied with "b"). UNIX(tm) diff does so
|
||||
// strip, so ends up claiming that ab is changed to acab by
|
||||
// inserting "ca" in the middle. That's minimal but unintuitive:
|
||||
// "it's obvious" that someone inserted "ac" at the front.
|
||||
// Windiff ends up at the same place as diff, but by pairing up
|
||||
// the unique 'b's and then matching the first two 'a's.
|
||||
besti, bestj, bestsize := alo, blo, 0
|
||||
|
||||
// find longest junk-free match
|
||||
// during an iteration of the loop, j2len[j] = length of longest
|
||||
// junk-free match ending with a[i-1] and b[j]
|
||||
j2len := map[int]int{}
|
||||
for i := alo; i != ahi; i++ {
|
||||
// look at all instances of a[i] in b; note that because
|
||||
// b2j has no junk keys, the loop is skipped if a[i] is junk
|
||||
newj2len := map[int]int{}
|
||||
for _, j := range m.b2j[m.a[i]] {
|
||||
// a[i] matches b[j]
|
||||
if j < blo {
|
||||
continue
|
||||
}
|
||||
if j >= bhi {
|
||||
break
|
||||
}
|
||||
k := j2len[j-1] + 1
|
||||
newj2len[j] = k
|
||||
if k > bestsize {
|
||||
besti, bestj, bestsize = i-k+1, j-k+1, k
|
||||
}
|
||||
}
|
||||
j2len = newj2len
|
||||
}
|
||||
|
||||
// Extend the best by non-junk elements on each end. In particular,
|
||||
// "popular" non-junk elements aren't in b2j, which greatly speeds
|
||||
// the inner loop above, but also means "the best" match so far
|
||||
// doesn't contain any junk *or* popular non-junk elements.
|
||||
for besti > alo && bestj > blo && !m.isBJunk(m.b[bestj-1]) &&
|
||||
m.a[besti-1] == m.b[bestj-1] {
|
||||
besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
|
||||
}
|
||||
for besti+bestsize < ahi && bestj+bestsize < bhi &&
|
||||
!m.isBJunk(m.b[bestj+bestsize]) &&
|
||||
m.a[besti+bestsize] == m.b[bestj+bestsize] {
|
||||
bestsize += 1
|
||||
}
|
||||
|
||||
// Now that we have a wholly interesting match (albeit possibly
|
||||
// empty!), we may as well suck up the matching junk on each
|
||||
// side of it too. Can't think of a good reason not to, and it
|
||||
// saves post-processing the (possibly considerable) expense of
|
||||
// figuring out what to do with it. In the case of an empty
|
||||
// interesting match, this is clearly the right thing to do,
|
||||
// because no other kind of match is possible in the regions.
|
||||
for besti > alo && bestj > blo && m.isBJunk(m.b[bestj-1]) &&
|
||||
m.a[besti-1] == m.b[bestj-1] {
|
||||
besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
|
||||
}
|
||||
for besti+bestsize < ahi && bestj+bestsize < bhi &&
|
||||
m.isBJunk(m.b[bestj+bestsize]) &&
|
||||
m.a[besti+bestsize] == m.b[bestj+bestsize] {
|
||||
bestsize += 1
|
||||
}
|
||||
|
||||
return Match{A: besti, B: bestj, Size: bestsize}
|
||||
}
|
||||
|
||||
// Return list of triples describing matching subsequences.
|
||||
//
|
||||
// Each triple is of the form (i, j, n), and means that
|
||||
// a[i:i+n] == b[j:j+n]. The triples are monotonically increasing in
|
||||
// i and in j. It's also guaranteed that if (i, j, n) and (i', j', n') are
|
||||
// adjacent triples in the list, and the second is not the last triple in the
|
||||
// list, then i+n != i' or j+n != j'. IOW, adjacent triples never describe
|
||||
// adjacent equal blocks.
|
||||
//
|
||||
// The last triple is a dummy, (len(a), len(b), 0), and is the only
|
||||
// triple with n==0.
|
||||
func (m *SequenceMatcher) GetMatchingBlocks() []Match {
|
||||
if m.matchingBlocks != nil {
|
||||
return m.matchingBlocks
|
||||
}
|
||||
|
||||
var matchBlocks func(alo, ahi, blo, bhi int, matched []Match) []Match
|
||||
matchBlocks = func(alo, ahi, blo, bhi int, matched []Match) []Match {
|
||||
match := m.findLongestMatch(alo, ahi, blo, bhi)
|
||||
i, j, k := match.A, match.B, match.Size
|
||||
if match.Size > 0 {
|
||||
if alo < i && blo < j {
|
||||
matched = matchBlocks(alo, i, blo, j, matched)
|
||||
}
|
||||
matched = append(matched, match)
|
||||
if i+k < ahi && j+k < bhi {
|
||||
matched = matchBlocks(i+k, ahi, j+k, bhi, matched)
|
||||
}
|
||||
}
|
||||
return matched
|
||||
}
|
||||
matched := matchBlocks(0, len(m.a), 0, len(m.b), nil)
|
||||
|
||||
// It's possible that we have adjacent equal blocks in the
|
||||
// matching_blocks list now.
|
||||
nonAdjacent := []Match{}
|
||||
i1, j1, k1 := 0, 0, 0
|
||||
for _, b := range matched {
|
||||
// Is this block adjacent to i1, j1, k1?
|
||||
i2, j2, k2 := b.A, b.B, b.Size
|
||||
if i1+k1 == i2 && j1+k1 == j2 {
|
||||
// Yes, so collapse them -- this just increases the length of
|
||||
// the first block by the length of the second, and the first
|
||||
// block so lengthened remains the block to compare against.
|
||||
k1 += k2
|
||||
} else {
|
||||
// Not adjacent. Remember the first block (k1==0 means it's
|
||||
// the dummy we started with), and make the second block the
|
||||
// new block to compare against.
|
||||
if k1 > 0 {
|
||||
nonAdjacent = append(nonAdjacent, Match{i1, j1, k1})
|
||||
}
|
||||
i1, j1, k1 = i2, j2, k2
|
||||
}
|
||||
}
|
||||
if k1 > 0 {
|
||||
nonAdjacent = append(nonAdjacent, Match{i1, j1, k1})
|
||||
}
|
||||
|
||||
nonAdjacent = append(nonAdjacent, Match{len(m.a), len(m.b), 0})
|
||||
m.matchingBlocks = nonAdjacent
|
||||
return m.matchingBlocks
|
||||
}
|
||||
|
||||
// Return list of 5-tuples describing how to turn a into b.
|
||||
//
|
||||
// Each tuple is of the form (tag, i1, i2, j1, j2). The first tuple
|
||||
// has i1 == j1 == 0, and remaining tuples have i1 == the i2 from the
|
||||
// tuple preceding it, and likewise for j1 == the previous j2.
|
||||
//
|
||||
// The tags are characters, with these meanings:
|
||||
//
|
||||
// 'r' (replace): a[i1:i2] should be replaced by b[j1:j2]
|
||||
//
|
||||
// 'd' (delete): a[i1:i2] should be deleted, j1==j2 in this case.
|
||||
//
|
||||
// 'i' (insert): b[j1:j2] should be inserted at a[i1:i1], i1==i2 in this case.
|
||||
//
|
||||
// 'e' (equal): a[i1:i2] == b[j1:j2]
|
||||
func (m *SequenceMatcher) GetOpCodes() []OpCode {
|
||||
if m.opCodes != nil {
|
||||
return m.opCodes
|
||||
}
|
||||
i, j := 0, 0
|
||||
matching := m.GetMatchingBlocks()
|
||||
opCodes := make([]OpCode, 0, len(matching))
|
||||
for _, m := range matching {
|
||||
// invariant: we've pumped out correct diffs to change
|
||||
// a[:i] into b[:j], and the next matching block is
|
||||
// a[ai:ai+size] == b[bj:bj+size]. So we need to pump
|
||||
// out a diff to change a[i:ai] into b[j:bj], pump out
|
||||
// the matching block, and move (i,j) beyond the match
|
||||
ai, bj, size := m.A, m.B, m.Size
|
||||
tag := byte(0)
|
||||
if i < ai && j < bj {
|
||||
tag = 'r'
|
||||
} else if i < ai {
|
||||
tag = 'd'
|
||||
} else if j < bj {
|
||||
tag = 'i'
|
||||
}
|
||||
if tag > 0 {
|
||||
opCodes = append(opCodes, OpCode{tag, i, ai, j, bj})
|
||||
}
|
||||
i, j = ai+size, bj+size
|
||||
// the list of matching blocks is terminated by a
|
||||
// sentinel with size 0
|
||||
if size > 0 {
|
||||
opCodes = append(opCodes, OpCode{'e', ai, i, bj, j})
|
||||
}
|
||||
}
|
||||
m.opCodes = opCodes
|
||||
return m.opCodes
|
||||
}
|
||||
|
||||
// Isolate change clusters by eliminating ranges with no changes.
|
||||
//
|
||||
// Return a generator of groups with up to n lines of context.
|
||||
// Each group is in the same format as returned by GetOpCodes().
|
||||
func (m *SequenceMatcher) GetGroupedOpCodes(n int) [][]OpCode {
|
||||
if n < 0 {
|
||||
n = 3
|
||||
}
|
||||
codes := m.GetOpCodes()
|
||||
if len(codes) == 0 {
|
||||
codes = []OpCode{OpCode{'e', 0, 1, 0, 1}}
|
||||
}
|
||||
// Fixup leading and trailing groups if they show no changes.
|
||||
if codes[0].Tag == 'e' {
|
||||
c := codes[0]
|
||||
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
|
||||
codes[0] = OpCode{c.Tag, max(i1, i2-n), i2, max(j1, j2-n), j2}
|
||||
}
|
||||
if codes[len(codes)-1].Tag == 'e' {
|
||||
c := codes[len(codes)-1]
|
||||
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
|
||||
codes[len(codes)-1] = OpCode{c.Tag, i1, min(i2, i1+n), j1, min(j2, j1+n)}
|
||||
}
|
||||
nn := n + n
|
||||
groups := [][]OpCode{}
|
||||
group := []OpCode{}
|
||||
for _, c := range codes {
|
||||
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
|
||||
// End the current group and start a new one whenever
|
||||
// there is a large range with no changes.
|
||||
if c.Tag == 'e' && i2-i1 > nn {
|
||||
group = append(group, OpCode{c.Tag, i1, min(i2, i1+n),
|
||||
j1, min(j2, j1+n)})
|
||||
groups = append(groups, group)
|
||||
group = []OpCode{}
|
||||
i1, j1 = max(i1, i2-n), max(j1, j2-n)
|
||||
}
|
||||
group = append(group, OpCode{c.Tag, i1, i2, j1, j2})
|
||||
}
|
||||
if len(group) > 0 && !(len(group) == 1 && group[0].Tag == 'e') {
|
||||
groups = append(groups, group)
|
||||
}
|
||||
return groups
|
||||
}
|
||||
|
||||
// Return a measure of the sequences' similarity (float in [0,1]).
|
||||
//
|
||||
// Where T is the total number of elements in both sequences, and
|
||||
// M is the number of matches, this is 2.0*M / T.
|
||||
// Note that this is 1 if the sequences are identical, and 0 if
|
||||
// they have nothing in common.
|
||||
//
|
||||
// .Ratio() is expensive to compute if you haven't already computed
|
||||
// .GetMatchingBlocks() or .GetOpCodes(), in which case you may
|
||||
// want to try .QuickRatio() or .RealQuickRation() first to get an
|
||||
// upper bound.
|
||||
func (m *SequenceMatcher) Ratio() float64 {
|
||||
matches := 0
|
||||
for _, m := range m.GetMatchingBlocks() {
|
||||
matches += m.Size
|
||||
}
|
||||
return calculateRatio(matches, len(m.a)+len(m.b))
|
||||
}
|
||||
|
||||
// Return an upper bound on ratio() relatively quickly.
|
||||
//
|
||||
// This isn't defined beyond that it is an upper bound on .Ratio(), and
|
||||
// is faster to compute.
|
||||
func (m *SequenceMatcher) QuickRatio() float64 {
|
||||
// viewing a and b as multisets, set matches to the cardinality
|
||||
// of their intersection; this counts the number of matches
|
||||
// without regard to order, so is clearly an upper bound
|
||||
if m.fullBCount == nil {
|
||||
m.fullBCount = map[string]int{}
|
||||
for _, s := range m.b {
|
||||
m.fullBCount[s] = m.fullBCount[s] + 1
|
||||
}
|
||||
}
|
||||
|
||||
// avail[x] is the number of times x appears in 'b' less the
|
||||
// number of times we've seen it in 'a' so far ... kinda
|
||||
avail := map[string]int{}
|
||||
matches := 0
|
||||
for _, s := range m.a {
|
||||
n, ok := avail[s]
|
||||
if !ok {
|
||||
n = m.fullBCount[s]
|
||||
}
|
||||
avail[s] = n - 1
|
||||
if n > 0 {
|
||||
matches += 1
|
||||
}
|
||||
}
|
||||
return calculateRatio(matches, len(m.a)+len(m.b))
|
||||
}
|
||||
|
||||
// Return an upper bound on ratio() very quickly.
|
||||
//
|
||||
// This isn't defined beyond that it is an upper bound on .Ratio(), and
|
||||
// is faster to compute than either .Ratio() or .QuickRatio().
|
||||
func (m *SequenceMatcher) RealQuickRatio() float64 {
|
||||
la, lb := len(m.a), len(m.b)
|
||||
return calculateRatio(min(la, lb), la+lb)
|
||||
}
|
||||
|
||||
// Convert range to the "ed" format
|
||||
func formatRangeUnified(start, stop int) string {
|
||||
// Per the diff spec at http://www.unix.org/single_unix_specification/
|
||||
beginning := start + 1 // lines start numbering with one
|
||||
length := stop - start
|
||||
if length == 1 {
|
||||
return fmt.Sprintf("%d", beginning)
|
||||
}
|
||||
if length == 0 {
|
||||
beginning -= 1 // empty ranges begin at line just before the range
|
||||
}
|
||||
return fmt.Sprintf("%d,%d", beginning, length)
|
||||
}
|
||||
|
||||
// Unified diff parameters
|
||||
type UnifiedDiff struct {
|
||||
A []string // First sequence lines
|
||||
FromFile string // First file name
|
||||
FromDate string // First file time
|
||||
B []string // Second sequence lines
|
||||
ToFile string // Second file name
|
||||
ToDate string // Second file time
|
||||
Eol string // Headers end of line, defaults to LF
|
||||
Context int // Number of context lines
|
||||
}
|
||||
|
||||
// Compare two sequences of lines; generate the delta as a unified diff.
|
||||
//
|
||||
// Unified diffs are a compact way of showing line changes and a few
|
||||
// lines of context. The number of context lines is set by 'n' which
|
||||
// defaults to three.
|
||||
//
|
||||
// By default, the diff control lines (those with ---, +++, or @@) are
|
||||
// created with a trailing newline. This is helpful so that inputs
|
||||
// created from file.readlines() result in diffs that are suitable for
|
||||
// file.writelines() since both the inputs and outputs have trailing
|
||||
// newlines.
|
||||
//
|
||||
// For inputs that do not have trailing newlines, set the lineterm
|
||||
// argument to "" so that the output will be uniformly newline free.
|
||||
//
|
||||
// The unidiff format normally has a header for filenames and modification
|
||||
// times. Any or all of these may be specified using strings for
|
||||
// 'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'.
|
||||
// The modification times are normally expressed in the ISO 8601 format.
|
||||
func WriteUnifiedDiff(writer io.Writer, diff UnifiedDiff) error {
|
||||
buf := bufio.NewWriter(writer)
|
||||
defer buf.Flush()
|
||||
wf := func(format string, args ...interface{}) error {
|
||||
_, err := buf.WriteString(fmt.Sprintf(format, args...))
|
||||
return err
|
||||
}
|
||||
ws := func(s string) error {
|
||||
_, err := buf.WriteString(s)
|
||||
return err
|
||||
}
|
||||
|
||||
if len(diff.Eol) == 0 {
|
||||
diff.Eol = "\n"
|
||||
}
|
||||
|
||||
started := false
|
||||
m := NewMatcher(diff.A, diff.B)
|
||||
for _, g := range m.GetGroupedOpCodes(diff.Context) {
|
||||
if !started {
|
||||
started = true
|
||||
fromDate := ""
|
||||
if len(diff.FromDate) > 0 {
|
||||
fromDate = "\t" + diff.FromDate
|
||||
}
|
||||
toDate := ""
|
||||
if len(diff.ToDate) > 0 {
|
||||
toDate = "\t" + diff.ToDate
|
||||
}
|
||||
if diff.FromFile != "" || diff.ToFile != "" {
|
||||
err := wf("--- %s%s%s", diff.FromFile, fromDate, diff.Eol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = wf("+++ %s%s%s", diff.ToFile, toDate, diff.Eol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
first, last := g[0], g[len(g)-1]
|
||||
range1 := formatRangeUnified(first.I1, last.I2)
|
||||
range2 := formatRangeUnified(first.J1, last.J2)
|
||||
if err := wf("@@ -%s +%s @@%s", range1, range2, diff.Eol); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, c := range g {
|
||||
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
|
||||
if c.Tag == 'e' {
|
||||
for _, line := range diff.A[i1:i2] {
|
||||
if err := ws(" " + line); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if c.Tag == 'r' || c.Tag == 'd' {
|
||||
for _, line := range diff.A[i1:i2] {
|
||||
if err := ws("-" + line); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if c.Tag == 'r' || c.Tag == 'i' {
|
||||
for _, line := range diff.B[j1:j2] {
|
||||
if err := ws("+" + line); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Like WriteUnifiedDiff but returns the diff a string.
|
||||
func GetUnifiedDiffString(diff UnifiedDiff) (string, error) {
|
||||
w := &bytes.Buffer{}
|
||||
err := WriteUnifiedDiff(w, diff)
|
||||
return string(w.Bytes()), err
|
||||
}
|
||||
|
||||
// Convert range to the "ed" format.
|
||||
func formatRangeContext(start, stop int) string {
|
||||
// Per the diff spec at http://www.unix.org/single_unix_specification/
|
||||
beginning := start + 1 // lines start numbering with one
|
||||
length := stop - start
|
||||
if length == 0 {
|
||||
beginning -= 1 // empty ranges begin at line just before the range
|
||||
}
|
||||
if length <= 1 {
|
||||
return fmt.Sprintf("%d", beginning)
|
||||
}
|
||||
return fmt.Sprintf("%d,%d", beginning, beginning+length-1)
|
||||
}
|
||||
|
||||
type ContextDiff UnifiedDiff
|
||||
|
||||
// Compare two sequences of lines; generate the delta as a context diff.
|
||||
//
|
||||
// Context diffs are a compact way of showing line changes and a few
|
||||
// lines of context. The number of context lines is set by diff.Context
|
||||
// which defaults to three.
|
||||
//
|
||||
// By default, the diff control lines (those with *** or ---) are
|
||||
// created with a trailing newline.
|
||||
//
|
||||
// For inputs that do not have trailing newlines, set the diff.Eol
|
||||
// argument to "" so that the output will be uniformly newline free.
|
||||
//
|
||||
// The context diff format normally has a header for filenames and
|
||||
// modification times. Any or all of these may be specified using
|
||||
// strings for diff.FromFile, diff.ToFile, diff.FromDate, diff.ToDate.
|
||||
// The modification times are normally expressed in the ISO 8601 format.
|
||||
// If not specified, the strings default to blanks.
|
||||
func WriteContextDiff(writer io.Writer, diff ContextDiff) error {
|
||||
buf := bufio.NewWriter(writer)
|
||||
defer buf.Flush()
|
||||
var diffErr error
|
||||
wf := func(format string, args ...interface{}) {
|
||||
_, err := buf.WriteString(fmt.Sprintf(format, args...))
|
||||
if diffErr == nil && err != nil {
|
||||
diffErr = err
|
||||
}
|
||||
}
|
||||
ws := func(s string) {
|
||||
_, err := buf.WriteString(s)
|
||||
if diffErr == nil && err != nil {
|
||||
diffErr = err
|
||||
}
|
||||
}
|
||||
|
||||
if len(diff.Eol) == 0 {
|
||||
diff.Eol = "\n"
|
||||
}
|
||||
|
||||
prefix := map[byte]string{
|
||||
'i': "+ ",
|
||||
'd': "- ",
|
||||
'r': "! ",
|
||||
'e': " ",
|
||||
}
|
||||
|
||||
started := false
|
||||
m := NewMatcher(diff.A, diff.B)
|
||||
for _, g := range m.GetGroupedOpCodes(diff.Context) {
|
||||
if !started {
|
||||
started = true
|
||||
fromDate := ""
|
||||
if len(diff.FromDate) > 0 {
|
||||
fromDate = "\t" + diff.FromDate
|
||||
}
|
||||
toDate := ""
|
||||
if len(diff.ToDate) > 0 {
|
||||
toDate = "\t" + diff.ToDate
|
||||
}
|
||||
if diff.FromFile != "" || diff.ToFile != "" {
|
||||
wf("*** %s%s%s", diff.FromFile, fromDate, diff.Eol)
|
||||
wf("--- %s%s%s", diff.ToFile, toDate, diff.Eol)
|
||||
}
|
||||
}
|
||||
|
||||
first, last := g[0], g[len(g)-1]
|
||||
ws("***************" + diff.Eol)
|
||||
|
||||
range1 := formatRangeContext(first.I1, last.I2)
|
||||
wf("*** %s ****%s", range1, diff.Eol)
|
||||
for _, c := range g {
|
||||
if c.Tag == 'r' || c.Tag == 'd' {
|
||||
for _, cc := range g {
|
||||
if cc.Tag == 'i' {
|
||||
continue
|
||||
}
|
||||
for _, line := range diff.A[cc.I1:cc.I2] {
|
||||
ws(prefix[cc.Tag] + line)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
range2 := formatRangeContext(first.J1, last.J2)
|
||||
wf("--- %s ----%s", range2, diff.Eol)
|
||||
for _, c := range g {
|
||||
if c.Tag == 'r' || c.Tag == 'i' {
|
||||
for _, cc := range g {
|
||||
if cc.Tag == 'd' {
|
||||
continue
|
||||
}
|
||||
for _, line := range diff.B[cc.J1:cc.J2] {
|
||||
ws(prefix[cc.Tag] + line)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return diffErr
|
||||
}
|
||||
|
||||
// Like WriteContextDiff but returns the diff a string.
|
||||
func GetContextDiffString(diff ContextDiff) (string, error) {
|
||||
w := &bytes.Buffer{}
|
||||
err := WriteContextDiff(w, diff)
|
||||
return string(w.Bytes()), err
|
||||
}
|
||||
|
||||
// Split a string on "\n" while preserving them. The output can be used
|
||||
// as input for UnifiedDiff and ContextDiff structures.
|
||||
func SplitLines(s string) []string {
|
||||
lines := strings.SplitAfter(s, "\n")
|
||||
lines[len(lines)-1] += "\n"
|
||||
return lines
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2012-2018 Mat Ryer and Tyler Bunnell
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,566 @@
|
|||
/*
|
||||
* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen
|
||||
* THIS FILE MUST NOT BE EDITED BY HAND
|
||||
*/
|
||||
|
||||
package assert
|
||||
|
||||
import (
|
||||
http "net/http"
|
||||
url "net/url"
|
||||
time "time"
|
||||
)
|
||||
|
||||
// Conditionf uses a Comparison to assert a complex condition.
|
||||
func Conditionf(t TestingT, comp Comparison, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Condition(t, comp, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Containsf asserts that the specified string, list(array, slice...) or map contains the
|
||||
// specified substring or element.
|
||||
//
|
||||
// assert.Containsf(t, "Hello World", "World", "error message %s", "formatted")
|
||||
// assert.Containsf(t, ["Hello", "World"], "World", "error message %s", "formatted")
|
||||
// assert.Containsf(t, {"Hello": "World"}, "Hello", "error message %s", "formatted")
|
||||
func Containsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Contains(t, s, contains, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// DirExistsf checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists.
|
||||
func DirExistsf(t TestingT, path string, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return DirExists(t, path, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// ElementsMatchf asserts that the specified listA(array, slice...) is equal to specified
|
||||
// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements,
|
||||
// the number of appearances of each of them in both lists should match.
|
||||
//
|
||||
// assert.ElementsMatchf(t, [1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted")
|
||||
func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return ElementsMatch(t, listA, listB, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either
|
||||
// a slice or a channel with len == 0.
|
||||
//
|
||||
// assert.Emptyf(t, obj, "error message %s", "formatted")
|
||||
func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Empty(t, object, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Equalf asserts that two objects are equal.
|
||||
//
|
||||
// assert.Equalf(t, 123, 123, "error message %s", "formatted")
|
||||
//
|
||||
// Pointer variable equality is determined based on the equality of the
|
||||
// referenced values (as opposed to the memory addresses). Function equality
|
||||
// cannot be determined and will always fail.
|
||||
func Equalf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Equal(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// EqualErrorf asserts that a function returned an error (i.e. not `nil`)
|
||||
// and that it is equal to the provided error.
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// assert.EqualErrorf(t, err, expectedErrorString, "error message %s", "formatted")
|
||||
func EqualErrorf(t TestingT, theError error, errString string, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return EqualError(t, theError, errString, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// EqualValuesf asserts that two objects are equal or convertable to the same types
|
||||
// and equal.
|
||||
//
|
||||
// assert.EqualValuesf(t, uint32(123, "error message %s", "formatted"), int32(123))
|
||||
func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return EqualValues(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Errorf asserts that a function returned an error (i.e. not `nil`).
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// if assert.Errorf(t, err, "error message %s", "formatted") {
|
||||
// assert.Equal(t, expectedErrorf, err)
|
||||
// }
|
||||
func Errorf(t TestingT, err error, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Error(t, err, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Eventuallyf asserts that given condition will be met in waitFor time,
|
||||
// periodically checking target function each tick.
|
||||
//
|
||||
// assert.Eventuallyf(t, func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted")
|
||||
func Eventuallyf(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Eventually(t, condition, waitFor, tick, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Exactlyf asserts that two objects are equal in value and type.
|
||||
//
|
||||
// assert.Exactlyf(t, int32(123, "error message %s", "formatted"), int64(123))
|
||||
func Exactlyf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Exactly(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Failf reports a failure through
|
||||
func Failf(t TestingT, failureMessage string, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Fail(t, failureMessage, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// FailNowf fails test
|
||||
func FailNowf(t TestingT, failureMessage string, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return FailNow(t, failureMessage, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Falsef asserts that the specified value is false.
|
||||
//
|
||||
// assert.Falsef(t, myBool, "error message %s", "formatted")
|
||||
func Falsef(t TestingT, value bool, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return False(t, value, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// FileExistsf checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file.
|
||||
func FileExistsf(t TestingT, path string, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return FileExists(t, path, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Greaterf asserts that the first element is greater than the second
|
||||
//
|
||||
// assert.Greaterf(t, 2, 1, "error message %s", "formatted")
|
||||
// assert.Greaterf(t, float64(2, "error message %s", "formatted"), float64(1))
|
||||
// assert.Greaterf(t, "b", "a", "error message %s", "formatted")
|
||||
func Greaterf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Greater(t, e1, e2, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// GreaterOrEqualf asserts that the first element is greater than or equal to the second
|
||||
//
|
||||
// assert.GreaterOrEqualf(t, 2, 1, "error message %s", "formatted")
|
||||
// assert.GreaterOrEqualf(t, 2, 2, "error message %s", "formatted")
|
||||
// assert.GreaterOrEqualf(t, "b", "a", "error message %s", "formatted")
|
||||
// assert.GreaterOrEqualf(t, "b", "b", "error message %s", "formatted")
|
||||
func GreaterOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return GreaterOrEqual(t, e1, e2, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// HTTPBodyContainsf asserts that a specified handler returns a
|
||||
// body that contains a string.
|
||||
//
|
||||
// assert.HTTPBodyContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return HTTPBodyContains(t, handler, method, url, values, str, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// HTTPBodyNotContainsf asserts that a specified handler returns a
|
||||
// body that does not contain a string.
|
||||
//
|
||||
// assert.HTTPBodyNotContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return HTTPBodyNotContains(t, handler, method, url, values, str, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// HTTPErrorf asserts that a specified handler returns an error status code.
|
||||
//
|
||||
// assert.HTTPErrorf(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
|
||||
func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return HTTPError(t, handler, method, url, values, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// HTTPRedirectf asserts that a specified handler returns a redirect status code.
|
||||
//
|
||||
// assert.HTTPRedirectf(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
|
||||
func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return HTTPRedirect(t, handler, method, url, values, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// HTTPSuccessf asserts that a specified handler returns a success status code.
|
||||
//
|
||||
// assert.HTTPSuccessf(t, myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return HTTPSuccess(t, handler, method, url, values, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Implementsf asserts that an object is implemented by the specified interface.
|
||||
//
|
||||
// assert.Implementsf(t, (*MyInterface, "error message %s", "formatted")(nil), new(MyObject))
|
||||
func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Implements(t, interfaceObject, object, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// InDeltaf asserts that the two numerals are within delta of each other.
|
||||
//
|
||||
// assert.InDeltaf(t, math.Pi, (22 / 7.0, "error message %s", "formatted"), 0.01)
|
||||
func InDeltaf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return InDelta(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys.
|
||||
func InDeltaMapValuesf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return InDeltaMapValues(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// InDeltaSlicef is the same as InDelta, except it compares two slices.
|
||||
func InDeltaSlicef(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return InDeltaSlice(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// InEpsilonf asserts that expected and actual have a relative error less than epsilon
|
||||
func InEpsilonf(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return InEpsilon(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices.
|
||||
func InEpsilonSlicef(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return InEpsilonSlice(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// IsTypef asserts that the specified objects are of the same type.
|
||||
func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return IsType(t, expectedType, object, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// JSONEqf asserts that two JSON strings are equivalent.
|
||||
//
|
||||
// assert.JSONEqf(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted")
|
||||
func JSONEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return JSONEq(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// YAMLEqf asserts that two YAML strings are equivalent.
|
||||
func YAMLEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return YAMLEq(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Lenf asserts that the specified object has specific length.
|
||||
// Lenf also fails if the object has a type that len() not accept.
|
||||
//
|
||||
// assert.Lenf(t, mySlice, 3, "error message %s", "formatted")
|
||||
func Lenf(t TestingT, object interface{}, length int, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Len(t, object, length, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Lessf asserts that the first element is less than the second
|
||||
//
|
||||
// assert.Lessf(t, 1, 2, "error message %s", "formatted")
|
||||
// assert.Lessf(t, float64(1, "error message %s", "formatted"), float64(2))
|
||||
// assert.Lessf(t, "a", "b", "error message %s", "formatted")
|
||||
func Lessf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Less(t, e1, e2, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// LessOrEqualf asserts that the first element is less than or equal to the second
|
||||
//
|
||||
// assert.LessOrEqualf(t, 1, 2, "error message %s", "formatted")
|
||||
// assert.LessOrEqualf(t, 2, 2, "error message %s", "formatted")
|
||||
// assert.LessOrEqualf(t, "a", "b", "error message %s", "formatted")
|
||||
// assert.LessOrEqualf(t, "b", "b", "error message %s", "formatted")
|
||||
func LessOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return LessOrEqual(t, e1, e2, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Nilf asserts that the specified object is nil.
|
||||
//
|
||||
// assert.Nilf(t, err, "error message %s", "formatted")
|
||||
func Nilf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Nil(t, object, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NoErrorf asserts that a function returned no error (i.e. `nil`).
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// if assert.NoErrorf(t, err, "error message %s", "formatted") {
|
||||
// assert.Equal(t, expectedObj, actualObj)
|
||||
// }
|
||||
func NoErrorf(t TestingT, err error, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NoError(t, err, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the
|
||||
// specified substring or element.
|
||||
//
|
||||
// assert.NotContainsf(t, "Hello World", "Earth", "error message %s", "formatted")
|
||||
// assert.NotContainsf(t, ["Hello", "World"], "Earth", "error message %s", "formatted")
|
||||
// assert.NotContainsf(t, {"Hello": "World"}, "Earth", "error message %s", "formatted")
|
||||
func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotContains(t, s, contains, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
|
||||
// a slice or a channel with len == 0.
|
||||
//
|
||||
// if assert.NotEmptyf(t, obj, "error message %s", "formatted") {
|
||||
// assert.Equal(t, "two", obj[1])
|
||||
// }
|
||||
func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotEmpty(t, object, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NotEqualf asserts that the specified values are NOT equal.
|
||||
//
|
||||
// assert.NotEqualf(t, obj1, obj2, "error message %s", "formatted")
|
||||
//
|
||||
// Pointer variable equality is determined based on the equality of the
|
||||
// referenced values (as opposed to the memory addresses).
|
||||
func NotEqualf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotEqual(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NotNilf asserts that the specified object is not nil.
|
||||
//
|
||||
// assert.NotNilf(t, err, "error message %s", "formatted")
|
||||
func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotNil(t, object, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic.
|
||||
//
|
||||
// assert.NotPanicsf(t, func(){ RemainCalm() }, "error message %s", "formatted")
|
||||
func NotPanicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotPanics(t, f, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NotRegexpf asserts that a specified regexp does not match a string.
|
||||
//
|
||||
// assert.NotRegexpf(t, regexp.MustCompile("starts", "error message %s", "formatted"), "it's starting")
|
||||
// assert.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted")
|
||||
func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotRegexp(t, rx, str, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NotSubsetf asserts that the specified list(array, slice...) contains not all
|
||||
// elements given in the specified subset(array, slice...).
|
||||
//
|
||||
// assert.NotSubsetf(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted")
|
||||
func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotSubset(t, list, subset, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NotZerof asserts that i is not the zero value for its type.
|
||||
func NotZerof(t TestingT, i interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotZero(t, i, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Panicsf asserts that the code inside the specified PanicTestFunc panics.
|
||||
//
|
||||
// assert.Panicsf(t, func(){ GoCrazy() }, "error message %s", "formatted")
|
||||
func Panicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Panics(t, f, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that
|
||||
// the recovered panic value equals the expected panic value.
|
||||
//
|
||||
// assert.PanicsWithValuef(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted")
|
||||
func PanicsWithValuef(t TestingT, expected interface{}, f PanicTestFunc, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return PanicsWithValue(t, expected, f, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Regexpf asserts that a specified regexp matches a string.
|
||||
//
|
||||
// assert.Regexpf(t, regexp.MustCompile("start", "error message %s", "formatted"), "it's starting")
|
||||
// assert.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted")
|
||||
func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Regexp(t, rx, str, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Samef asserts that two pointers reference the same object.
|
||||
//
|
||||
// assert.Samef(t, ptr1, ptr2, "error message %s", "formatted")
|
||||
//
|
||||
// Both arguments must be pointer variables. Pointer variable sameness is
|
||||
// determined based on the equality of both type and value.
|
||||
func Samef(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Same(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Subsetf asserts that the specified list(array, slice...) contains all
|
||||
// elements given in the specified subset(array, slice...).
|
||||
//
|
||||
// assert.Subsetf(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted")
|
||||
func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Subset(t, list, subset, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Truef asserts that the specified value is true.
|
||||
//
|
||||
// assert.Truef(t, myBool, "error message %s", "formatted")
|
||||
func Truef(t TestingT, value bool, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return True(t, value, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// WithinDurationf asserts that the two times are within duration delta of each other.
|
||||
//
|
||||
// assert.WithinDurationf(t, time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted")
|
||||
func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return WithinDuration(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Zerof asserts that i is the zero value for its type.
|
||||
func Zerof(t TestingT, i interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Zero(t, i, append([]interface{}{msg}, args...)...)
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{{.CommentFormat}}
|
||||
func {{.DocInfo.Name}}f(t TestingT, {{.ParamsFormat}}) bool {
|
||||
if h, ok := t.(tHelper); ok { h.Helper() }
|
||||
return {{.DocInfo.Name}}(t, {{.ForwardedParamsFormat}})
|
||||
}
|
File diff suppressed because it is too large
Load Diff
5
vendor/github.com/stretchr/testify/assert/assertion_forward.go.tmpl
generated
vendored
Normal file
5
vendor/github.com/stretchr/testify/assert/assertion_forward.go.tmpl
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
{{.CommentWithoutT "a"}}
|
||||
func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) bool {
|
||||
if h, ok := a.t.(tHelper); ok { h.Helper() }
|
||||
return {{.DocInfo.Name}}(a.t, {{.ForwardedParams}})
|
||||
}
|
|
@ -0,0 +1,309 @@
|
|||
package assert
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func compare(obj1, obj2 interface{}, kind reflect.Kind) (int, bool) {
|
||||
switch kind {
|
||||
case reflect.Int:
|
||||
{
|
||||
intobj1 := obj1.(int)
|
||||
intobj2 := obj2.(int)
|
||||
if intobj1 > intobj2 {
|
||||
return -1, true
|
||||
}
|
||||
if intobj1 == intobj2 {
|
||||
return 0, true
|
||||
}
|
||||
if intobj1 < intobj2 {
|
||||
return 1, true
|
||||
}
|
||||
}
|
||||
case reflect.Int8:
|
||||
{
|
||||
int8obj1 := obj1.(int8)
|
||||
int8obj2 := obj2.(int8)
|
||||
if int8obj1 > int8obj2 {
|
||||
return -1, true
|
||||
}
|
||||
if int8obj1 == int8obj2 {
|
||||
return 0, true
|
||||
}
|
||||
if int8obj1 < int8obj2 {
|
||||
return 1, true
|
||||
}
|
||||
}
|
||||
case reflect.Int16:
|
||||
{
|
||||
int16obj1 := obj1.(int16)
|
||||
int16obj2 := obj2.(int16)
|
||||
if int16obj1 > int16obj2 {
|
||||
return -1, true
|
||||
}
|
||||
if int16obj1 == int16obj2 {
|
||||
return 0, true
|
||||
}
|
||||
if int16obj1 < int16obj2 {
|
||||
return 1, true
|
||||
}
|
||||
}
|
||||
case reflect.Int32:
|
||||
{
|
||||
int32obj1 := obj1.(int32)
|
||||
int32obj2 := obj2.(int32)
|
||||
if int32obj1 > int32obj2 {
|
||||
return -1, true
|
||||
}
|
||||
if int32obj1 == int32obj2 {
|
||||
return 0, true
|
||||
}
|
||||
if int32obj1 < int32obj2 {
|
||||
return 1, true
|
||||
}
|
||||
}
|
||||
case reflect.Int64:
|
||||
{
|
||||
int64obj1 := obj1.(int64)
|
||||
int64obj2 := obj2.(int64)
|
||||
if int64obj1 > int64obj2 {
|
||||
return -1, true
|
||||
}
|
||||
if int64obj1 == int64obj2 {
|
||||
return 0, true
|
||||
}
|
||||
if int64obj1 < int64obj2 {
|
||||
return 1, true
|
||||
}
|
||||
}
|
||||
case reflect.Uint:
|
||||
{
|
||||
uintobj1 := obj1.(uint)
|
||||
uintobj2 := obj2.(uint)
|
||||
if uintobj1 > uintobj2 {
|
||||
return -1, true
|
||||
}
|
||||
if uintobj1 == uintobj2 {
|
||||
return 0, true
|
||||
}
|
||||
if uintobj1 < uintobj2 {
|
||||
return 1, true
|
||||
}
|
||||
}
|
||||
case reflect.Uint8:
|
||||
{
|
||||
uint8obj1 := obj1.(uint8)
|
||||
uint8obj2 := obj2.(uint8)
|
||||
if uint8obj1 > uint8obj2 {
|
||||
return -1, true
|
||||
}
|
||||
if uint8obj1 == uint8obj2 {
|
||||
return 0, true
|
||||
}
|
||||
if uint8obj1 < uint8obj2 {
|
||||
return 1, true
|
||||
}
|
||||
}
|
||||
case reflect.Uint16:
|
||||
{
|
||||
uint16obj1 := obj1.(uint16)
|
||||
uint16obj2 := obj2.(uint16)
|
||||
if uint16obj1 > uint16obj2 {
|
||||
return -1, true
|
||||
}
|
||||
if uint16obj1 == uint16obj2 {
|
||||
return 0, true
|
||||
}
|
||||
if uint16obj1 < uint16obj2 {
|
||||
return 1, true
|
||||
}
|
||||
}
|
||||
case reflect.Uint32:
|
||||
{
|
||||
uint32obj1 := obj1.(uint32)
|
||||
uint32obj2 := obj2.(uint32)
|
||||
if uint32obj1 > uint32obj2 {
|
||||
return -1, true
|
||||
}
|
||||
if uint32obj1 == uint32obj2 {
|
||||
return 0, true
|
||||
}
|
||||
if uint32obj1 < uint32obj2 {
|
||||
return 1, true
|
||||
}
|
||||
}
|
||||
case reflect.Uint64:
|
||||
{
|
||||
uint64obj1 := obj1.(uint64)
|
||||
uint64obj2 := obj2.(uint64)
|
||||
if uint64obj1 > uint64obj2 {
|
||||
return -1, true
|
||||
}
|
||||
if uint64obj1 == uint64obj2 {
|
||||
return 0, true
|
||||
}
|
||||
if uint64obj1 < uint64obj2 {
|
||||
return 1, true
|
||||
}
|
||||
}
|
||||
case reflect.Float32:
|
||||
{
|
||||
float32obj1 := obj1.(float32)
|
||||
float32obj2 := obj2.(float32)
|
||||
if float32obj1 > float32obj2 {
|
||||
return -1, true
|
||||
}
|
||||
if float32obj1 == float32obj2 {
|
||||
return 0, true
|
||||
}
|
||||
if float32obj1 < float32obj2 {
|
||||
return 1, true
|
||||
}
|
||||
}
|
||||
case reflect.Float64:
|
||||
{
|
||||
float64obj1 := obj1.(float64)
|
||||
float64obj2 := obj2.(float64)
|
||||
if float64obj1 > float64obj2 {
|
||||
return -1, true
|
||||
}
|
||||
if float64obj1 == float64obj2 {
|
||||
return 0, true
|
||||
}
|
||||
if float64obj1 < float64obj2 {
|
||||
return 1, true
|
||||
}
|
||||
}
|
||||
case reflect.String:
|
||||
{
|
||||
stringobj1 := obj1.(string)
|
||||
stringobj2 := obj2.(string)
|
||||
if stringobj1 > stringobj2 {
|
||||
return -1, true
|
||||
}
|
||||
if stringobj1 == stringobj2 {
|
||||
return 0, true
|
||||
}
|
||||
if stringobj1 < stringobj2 {
|
||||
return 1, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// Greater asserts that the first element is greater than the second
|
||||
//
|
||||
// assert.Greater(t, 2, 1)
|
||||
// assert.Greater(t, float64(2), float64(1))
|
||||
// assert.Greater(t, "b", "a")
|
||||
func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
|
||||
e1Kind := reflect.ValueOf(e1).Kind()
|
||||
e2Kind := reflect.ValueOf(e2).Kind()
|
||||
if e1Kind != e2Kind {
|
||||
return Fail(t, "Elements should be the same type", msgAndArgs...)
|
||||
}
|
||||
|
||||
res, isComparable := compare(e1, e2, e1Kind)
|
||||
if !isComparable {
|
||||
return Fail(t, fmt.Sprintf("Can not compare type \"%s\"", reflect.TypeOf(e1)), msgAndArgs...)
|
||||
}
|
||||
|
||||
if res != -1 {
|
||||
return Fail(t, fmt.Sprintf("\"%v\" is not greater than \"%v\"", e1, e2), msgAndArgs...)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// GreaterOrEqual asserts that the first element is greater than or equal to the second
|
||||
//
|
||||
// assert.GreaterOrEqual(t, 2, 1)
|
||||
// assert.GreaterOrEqual(t, 2, 2)
|
||||
// assert.GreaterOrEqual(t, "b", "a")
|
||||
// assert.GreaterOrEqual(t, "b", "b")
|
||||
func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
|
||||
e1Kind := reflect.ValueOf(e1).Kind()
|
||||
e2Kind := reflect.ValueOf(e2).Kind()
|
||||
if e1Kind != e2Kind {
|
||||
return Fail(t, "Elements should be the same type", msgAndArgs...)
|
||||
}
|
||||
|
||||
res, isComparable := compare(e1, e2, e1Kind)
|
||||
if !isComparable {
|
||||
return Fail(t, fmt.Sprintf("Can not compare type \"%s\"", reflect.TypeOf(e1)), msgAndArgs...)
|
||||
}
|
||||
|
||||
if res != -1 && res != 0 {
|
||||
return Fail(t, fmt.Sprintf("\"%v\" is not greater than or equal to \"%v\"", e1, e2), msgAndArgs...)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Less asserts that the first element is less than the second
|
||||
//
|
||||
// assert.Less(t, 1, 2)
|
||||
// assert.Less(t, float64(1), float64(2))
|
||||
// assert.Less(t, "a", "b")
|
||||
func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
|
||||
e1Kind := reflect.ValueOf(e1).Kind()
|
||||
e2Kind := reflect.ValueOf(e2).Kind()
|
||||
if e1Kind != e2Kind {
|
||||
return Fail(t, "Elements should be the same type", msgAndArgs...)
|
||||
}
|
||||
|
||||
res, isComparable := compare(e1, e2, e1Kind)
|
||||
if !isComparable {
|
||||
return Fail(t, fmt.Sprintf("Can not compare type \"%s\"", reflect.TypeOf(e1)), msgAndArgs...)
|
||||
}
|
||||
|
||||
if res != 1 {
|
||||
return Fail(t, fmt.Sprintf("\"%v\" is not less than \"%v\"", e1, e2), msgAndArgs...)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// LessOrEqual asserts that the first element is less than or equal to the second
|
||||
//
|
||||
// assert.LessOrEqual(t, 1, 2)
|
||||
// assert.LessOrEqual(t, 2, 2)
|
||||
// assert.LessOrEqual(t, "a", "b")
|
||||
// assert.LessOrEqual(t, "b", "b")
|
||||
func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
|
||||
e1Kind := reflect.ValueOf(e1).Kind()
|
||||
e2Kind := reflect.ValueOf(e2).Kind()
|
||||
if e1Kind != e2Kind {
|
||||
return Fail(t, "Elements should be the same type", msgAndArgs...)
|
||||
}
|
||||
|
||||
res, isComparable := compare(e1, e2, e1Kind)
|
||||
if !isComparable {
|
||||
return Fail(t, fmt.Sprintf("Can not compare type \"%s\"", reflect.TypeOf(e1)), msgAndArgs...)
|
||||
}
|
||||
|
||||
if res != 1 && res != 0 {
|
||||
return Fail(t, fmt.Sprintf("\"%v\" is not less than or equal to \"%v\"", e1, e2), msgAndArgs...)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
118
vendor/github.com/stretchr/testify/assert/assertion_order_test.go
generated
vendored
Normal file
118
vendor/github.com/stretchr/testify/assert/assertion_order_test.go
generated
vendored
Normal file
|
@ -0,0 +1,118 @@
|
|||
package assert
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCompare(t *testing.T) {
|
||||
for _, currCase := range []struct {
|
||||
less interface{}
|
||||
greater interface{}
|
||||
cType string
|
||||
}{
|
||||
{less: "a", greater: "b", cType: "string"},
|
||||
{less: int(1), greater: int(2), cType: "int"},
|
||||
{less: int8(1), greater: int8(2), cType: "int8"},
|
||||
{less: int16(1), greater: int16(2), cType: "int16"},
|
||||
{less: int32(1), greater: int32(2), cType: "int32"},
|
||||
{less: int64(1), greater: int64(2), cType: "int64"},
|
||||
{less: uint8(1), greater: uint8(2), cType: "uint8"},
|
||||
{less: uint16(1), greater: uint16(2), cType: "uint16"},
|
||||
{less: uint32(1), greater: uint32(2), cType: "uint32"},
|
||||
{less: uint64(1), greater: uint64(2), cType: "uint64"},
|
||||
{less: float32(1), greater: float32(2), cType: "float32"},
|
||||
{less: float64(1), greater: float64(2), cType: "float64"},
|
||||
} {
|
||||
resLess, isComparable := compare(currCase.less, currCase.greater, reflect.ValueOf(currCase.less).Kind())
|
||||
if !isComparable {
|
||||
t.Error("object should be comparable for type " + currCase.cType)
|
||||
}
|
||||
|
||||
if resLess != 1 {
|
||||
t.Errorf("object less should be less than greater for type " + currCase.cType)
|
||||
}
|
||||
|
||||
resGreater, isComparable := compare(currCase.greater, currCase.less, reflect.ValueOf(currCase.less).Kind())
|
||||
if !isComparable {
|
||||
t.Error("object are comparable for type " + currCase.cType)
|
||||
}
|
||||
|
||||
if resGreater != -1 {
|
||||
t.Errorf("object greater should be greater than less for type " + currCase.cType)
|
||||
}
|
||||
|
||||
resEqual, isComparable := compare(currCase.less, currCase.less, reflect.ValueOf(currCase.less).Kind())
|
||||
if !isComparable {
|
||||
t.Error("object are comparable for type " + currCase.cType)
|
||||
}
|
||||
|
||||
if resEqual != 0 {
|
||||
t.Errorf("objects should be equal for type " + currCase.cType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGreater(t *testing.T) {
|
||||
mockT := new(testing.T)
|
||||
|
||||
if !Greater(mockT, 2, 1) {
|
||||
t.Error("Greater should return true")
|
||||
}
|
||||
|
||||
if Greater(mockT, 1, 1) {
|
||||
t.Error("Greater should return false")
|
||||
}
|
||||
|
||||
if Greater(mockT, 1, 2) {
|
||||
t.Error("Greater should return false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGreaterOrEqual(t *testing.T) {
|
||||
mockT := new(testing.T)
|
||||
|
||||
if !GreaterOrEqual(mockT, 2, 1) {
|
||||
t.Error("Greater should return true")
|
||||
}
|
||||
|
||||
if !GreaterOrEqual(mockT, 1, 1) {
|
||||
t.Error("Greater should return true")
|
||||
}
|
||||
|
||||
if GreaterOrEqual(mockT, 1, 2) {
|
||||
t.Error("Greater should return false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLess(t *testing.T) {
|
||||
mockT := new(testing.T)
|
||||
|
||||
if !Less(mockT, 1, 2) {
|
||||
t.Error("Less should return true")
|
||||
}
|
||||
|
||||
if Less(mockT, 1, 1) {
|
||||
t.Error("Less should return false")
|
||||
}
|
||||
|
||||
if Less(mockT, 2, 1) {
|
||||
t.Error("Less should return false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLessOrEqual(t *testing.T) {
|
||||
mockT := new(testing.T)
|
||||
|
||||
if !LessOrEqual(mockT, 1, 2) {
|
||||
t.Error("Greater should return true")
|
||||
}
|
||||
|
||||
if !LessOrEqual(mockT, 1, 1) {
|
||||
t.Error("Greater should return true")
|
||||
}
|
||||
|
||||
if LessOrEqual(mockT, 2, 1) {
|
||||
t.Error("Greater should return false")
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,45 @@
|
|||
// Package assert provides a set of comprehensive testing tools for use with the normal Go testing system.
|
||||
//
|
||||
// Example Usage
|
||||
//
|
||||
// The following is a complete example using assert in a standard test function:
|
||||
// import (
|
||||
// "testing"
|
||||
// "github.com/stretchr/testify/assert"
|
||||
// )
|
||||
//
|
||||
// func TestSomething(t *testing.T) {
|
||||
//
|
||||
// var a string = "Hello"
|
||||
// var b string = "Hello"
|
||||
//
|
||||
// assert.Equal(t, a, b, "The two words should be the same.")
|
||||
//
|
||||
// }
|
||||
//
|
||||
// if you assert many times, use the format below:
|
||||
//
|
||||
// import (
|
||||
// "testing"
|
||||
// "github.com/stretchr/testify/assert"
|
||||
// )
|
||||
//
|
||||
// func TestSomething(t *testing.T) {
|
||||
// assert := assert.New(t)
|
||||
//
|
||||
// var a string = "Hello"
|
||||
// var b string = "Hello"
|
||||
//
|
||||
// assert.Equal(a, b, "The two words should be the same.")
|
||||
// }
|
||||
//
|
||||
// Assertions
|
||||
//
|
||||
// Assertions allow you to easily write test code, and are global funcs in the `assert` package.
|
||||
// All assertion functions take, as the first argument, the `*testing.T` object provided by the
|
||||
// testing framework. This allows the assertion funcs to write the failings and other details to
|
||||
// the correct place.
|
||||
//
|
||||
// Every assertion function also takes an optional string message as the final argument,
|
||||
// allowing custom error messages to be appended to the message the assertion method outputs.
|
||||
package assert
|
|
@ -0,0 +1,10 @@
|
|||
package assert
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// AnError is an error instance useful for testing. If the code does not care
|
||||
// about error specifics, and only needs to return the error for example, this
|
||||
// error should be used to make the test code more readable.
|
||||
var AnError = errors.New("assert.AnError general error for testing")
|
|
@ -0,0 +1,16 @@
|
|||
package assert
|
||||
|
||||
// Assertions provides assertion methods around the
|
||||
// TestingT interface.
|
||||
type Assertions struct {
|
||||
t TestingT
|
||||
}
|
||||
|
||||
// New makes a new Assertions object for the specified TestingT.
|
||||
func New(t TestingT) *Assertions {
|
||||
return &Assertions{
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
//go:generate go run ../_codegen/main.go -output-package=assert -template=assertion_forward.go.tmpl -include-format-funcs
|
709
vendor/github.com/stretchr/testify/assert/forward_assertions_test.go
generated
vendored
Normal file
709
vendor/github.com/stretchr/testify/assert/forward_assertions_test.go
generated
vendored
Normal file
|
@ -0,0 +1,709 @@
|
|||
package assert
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"regexp"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestImplementsWrapper(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
|
||||
if !assert.Implements((*AssertionTesterInterface)(nil), new(AssertionTesterConformingObject)) {
|
||||
t.Error("Implements method should return true: AssertionTesterConformingObject implements AssertionTesterInterface")
|
||||
}
|
||||
if assert.Implements((*AssertionTesterInterface)(nil), new(AssertionTesterNonConformingObject)) {
|
||||
t.Error("Implements method should return false: AssertionTesterNonConformingObject does not implements AssertionTesterInterface")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsTypeWrapper(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
|
||||
if !assert.IsType(new(AssertionTesterConformingObject), new(AssertionTesterConformingObject)) {
|
||||
t.Error("IsType should return true: AssertionTesterConformingObject is the same type as AssertionTesterConformingObject")
|
||||
}
|
||||
if assert.IsType(new(AssertionTesterConformingObject), new(AssertionTesterNonConformingObject)) {
|
||||
t.Error("IsType should return false: AssertionTesterConformingObject is not the same type as AssertionTesterNonConformingObject")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestEqualWrapper(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
|
||||
if !assert.Equal("Hello World", "Hello World") {
|
||||
t.Error("Equal should return true")
|
||||
}
|
||||
if !assert.Equal(123, 123) {
|
||||
t.Error("Equal should return true")
|
||||
}
|
||||
if !assert.Equal(123.5, 123.5) {
|
||||
t.Error("Equal should return true")
|
||||
}
|
||||
if !assert.Equal([]byte("Hello World"), []byte("Hello World")) {
|
||||
t.Error("Equal should return true")
|
||||
}
|
||||
if !assert.Equal(nil, nil) {
|
||||
t.Error("Equal should return true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEqualValuesWrapper(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
|
||||
if !assert.EqualValues(uint32(10), int32(10)) {
|
||||
t.Error("EqualValues should return true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotNilWrapper(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
|
||||
if !assert.NotNil(new(AssertionTesterConformingObject)) {
|
||||
t.Error("NotNil should return true: object is not nil")
|
||||
}
|
||||
if assert.NotNil(nil) {
|
||||
t.Error("NotNil should return false: object is nil")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestNilWrapper(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
|
||||
if !assert.Nil(nil) {
|
||||
t.Error("Nil should return true: object is nil")
|
||||
}
|
||||
if assert.Nil(new(AssertionTesterConformingObject)) {
|
||||
t.Error("Nil should return false: object is not nil")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestTrueWrapper(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
|
||||
if !assert.True(true) {
|
||||
t.Error("True should return true")
|
||||
}
|
||||
if assert.True(false) {
|
||||
t.Error("True should return false")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestFalseWrapper(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
|
||||
if !assert.False(false) {
|
||||
t.Error("False should return true")
|
||||
}
|
||||
if assert.False(true) {
|
||||
t.Error("False should return false")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestExactlyWrapper(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
|
||||
a := float32(1)
|
||||
b := float64(1)
|
||||
c := float32(1)
|
||||
d := float32(2)
|
||||
|
||||
if assert.Exactly(a, b) {
|
||||
t.Error("Exactly should return false")
|
||||
}
|
||||
if assert.Exactly(a, d) {
|
||||
t.Error("Exactly should return false")
|
||||
}
|
||||
if !assert.Exactly(a, c) {
|
||||
t.Error("Exactly should return true")
|
||||
}
|
||||
|
||||
if assert.Exactly(nil, a) {
|
||||
t.Error("Exactly should return false")
|
||||
}
|
||||
if assert.Exactly(a, nil) {
|
||||
t.Error("Exactly should return false")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestNotEqualWrapper(t *testing.T) {
|
||||
|
||||
assert := New(new(testing.T))
|
||||
|
||||
if !assert.NotEqual("Hello World", "Hello World!") {
|
||||
t.Error("NotEqual should return true")
|
||||
}
|
||||
if !assert.NotEqual(123, 1234) {
|
||||
t.Error("NotEqual should return true")
|
||||
}
|
||||
if !assert.NotEqual(123.5, 123.55) {
|
||||
t.Error("NotEqual should return true")
|
||||
}
|
||||
if !assert.NotEqual([]byte("Hello World"), []byte("Hello World!")) {
|
||||
t.Error("NotEqual should return true")
|
||||
}
|
||||
if !assert.NotEqual(nil, new(AssertionTesterConformingObject)) {
|
||||
t.Error("NotEqual should return true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainsWrapper(t *testing.T) {
|
||||
|
||||
assert := New(new(testing.T))
|
||||
list := []string{"Foo", "Bar"}
|
||||
|
||||
if !assert.Contains("Hello World", "Hello") {
|
||||
t.Error("Contains should return true: \"Hello World\" contains \"Hello\"")
|
||||
}
|
||||
if assert.Contains("Hello World", "Salut") {
|
||||
t.Error("Contains should return false: \"Hello World\" does not contain \"Salut\"")
|
||||
}
|
||||
|
||||
if !assert.Contains(list, "Foo") {
|
||||
t.Error("Contains should return true: \"[\"Foo\", \"Bar\"]\" contains \"Foo\"")
|
||||
}
|
||||
if assert.Contains(list, "Salut") {
|
||||
t.Error("Contains should return false: \"[\"Foo\", \"Bar\"]\" does not contain \"Salut\"")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestNotContainsWrapper(t *testing.T) {
|
||||
|
||||
assert := New(new(testing.T))
|
||||
list := []string{"Foo", "Bar"}
|
||||
|
||||
if !assert.NotContains("Hello World", "Hello!") {
|
||||
t.Error("NotContains should return true: \"Hello World\" does not contain \"Hello!\"")
|
||||
}
|
||||
if assert.NotContains("Hello World", "Hello") {
|
||||
t.Error("NotContains should return false: \"Hello World\" contains \"Hello\"")
|
||||
}
|
||||
|
||||
if !assert.NotContains(list, "Foo!") {
|
||||
t.Error("NotContains should return true: \"[\"Foo\", \"Bar\"]\" does not contain \"Foo!\"")
|
||||
}
|
||||
if assert.NotContains(list, "Foo") {
|
||||
t.Error("NotContains should return false: \"[\"Foo\", \"Bar\"]\" contains \"Foo\"")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestConditionWrapper(t *testing.T) {
|
||||
|
||||
assert := New(new(testing.T))
|
||||
|
||||
if !assert.Condition(func() bool { return true }, "Truth") {
|
||||
t.Error("Condition should return true")
|
||||
}
|
||||
|
||||
if assert.Condition(func() bool { return false }, "Lie") {
|
||||
t.Error("Condition should return false")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestDidPanicWrapper(t *testing.T) {
|
||||
|
||||
if funcDidPanic, _ := didPanic(func() {
|
||||
panic("Panic!")
|
||||
}); !funcDidPanic {
|
||||
t.Error("didPanic should return true")
|
||||
}
|
||||
|
||||
if funcDidPanic, _ := didPanic(func() {
|
||||
}); funcDidPanic {
|
||||
t.Error("didPanic should return false")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestPanicsWrapper(t *testing.T) {
|
||||
|
||||
assert := New(new(testing.T))
|
||||
|
||||
if !assert.Panics(func() {
|
||||
panic("Panic!")
|
||||
}) {
|
||||
t.Error("Panics should return true")
|
||||
}
|
||||
|
||||
if assert.Panics(func() {
|
||||
}) {
|
||||
t.Error("Panics should return false")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestNotPanicsWrapper(t *testing.T) {
|
||||
|
||||
assert := New(new(testing.T))
|
||||
|
||||
if !assert.NotPanics(func() {
|
||||
}) {
|
||||
t.Error("NotPanics should return true")
|
||||
}
|
||||
|
||||
if assert.NotPanics(func() {
|
||||
panic("Panic!")
|
||||
}) {
|
||||
t.Error("NotPanics should return false")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestNoErrorWrapper(t *testing.T) {
|
||||
assert := New(t)
|
||||
mockAssert := New(new(testing.T))
|
||||
|
||||
// start with a nil error
|
||||
var err error
|
||||
|
||||
assert.True(mockAssert.NoError(err), "NoError should return True for nil arg")
|
||||
|
||||
// now set an error
|
||||
err = errors.New("Some error")
|
||||
|
||||
assert.False(mockAssert.NoError(err), "NoError with error should return False")
|
||||
|
||||
}
|
||||
|
||||
func TestErrorWrapper(t *testing.T) {
|
||||
assert := New(t)
|
||||
mockAssert := New(new(testing.T))
|
||||
|
||||
// start with a nil error
|
||||
var err error
|
||||
|
||||
assert.False(mockAssert.Error(err), "Error should return False for nil arg")
|
||||
|
||||
// now set an error
|
||||
err = errors.New("Some error")
|
||||
|
||||
assert.True(mockAssert.Error(err), "Error with error should return True")
|
||||
|
||||
}
|
||||
|
||||
func TestEqualErrorWrapper(t *testing.T) {
|
||||
assert := New(t)
|
||||
mockAssert := New(new(testing.T))
|
||||
|
||||
// start with a nil error
|
||||
var err error
|
||||
assert.False(mockAssert.EqualError(err, ""),
|
||||
"EqualError should return false for nil arg")
|
||||
|
||||
// now set an error
|
||||
err = errors.New("some error")
|
||||
assert.False(mockAssert.EqualError(err, "Not some error"),
|
||||
"EqualError should return false for different error string")
|
||||
assert.True(mockAssert.EqualError(err, "some error"),
|
||||
"EqualError should return true")
|
||||
}
|
||||
|
||||
func TestEmptyWrapper(t *testing.T) {
|
||||
assert := New(t)
|
||||
mockAssert := New(new(testing.T))
|
||||
|
||||
assert.True(mockAssert.Empty(""), "Empty string is empty")
|
||||
assert.True(mockAssert.Empty(nil), "Nil is empty")
|
||||
assert.True(mockAssert.Empty([]string{}), "Empty string array is empty")
|
||||
assert.True(mockAssert.Empty(0), "Zero int value is empty")
|
||||
assert.True(mockAssert.Empty(false), "False value is empty")
|
||||
|
||||
assert.False(mockAssert.Empty("something"), "Non Empty string is not empty")
|
||||
assert.False(mockAssert.Empty(errors.New("something")), "Non nil object is not empty")
|
||||
assert.False(mockAssert.Empty([]string{"something"}), "Non empty string array is not empty")
|
||||
assert.False(mockAssert.Empty(1), "Non-zero int value is not empty")
|
||||
assert.False(mockAssert.Empty(true), "True value is not empty")
|
||||
|
||||
}
|
||||
|
||||
func TestNotEmptyWrapper(t *testing.T) {
|
||||
assert := New(t)
|
||||
mockAssert := New(new(testing.T))
|
||||
|
||||
assert.False(mockAssert.NotEmpty(""), "Empty string is empty")
|
||||
assert.False(mockAssert.NotEmpty(nil), "Nil is empty")
|
||||
assert.False(mockAssert.NotEmpty([]string{}), "Empty string array is empty")
|
||||
assert.False(mockAssert.NotEmpty(0), "Zero int value is empty")
|
||||
assert.False(mockAssert.NotEmpty(false), "False value is empty")
|
||||
|
||||
assert.True(mockAssert.NotEmpty("something"), "Non Empty string is not empty")
|
||||
assert.True(mockAssert.NotEmpty(errors.New("something")), "Non nil object is not empty")
|
||||
assert.True(mockAssert.NotEmpty([]string{"something"}), "Non empty string array is not empty")
|
||||
assert.True(mockAssert.NotEmpty(1), "Non-zero int value is not empty")
|
||||
assert.True(mockAssert.NotEmpty(true), "True value is not empty")
|
||||
|
||||
}
|
||||
|
||||
func TestLenWrapper(t *testing.T) {
|
||||
assert := New(t)
|
||||
mockAssert := New(new(testing.T))
|
||||
|
||||
assert.False(mockAssert.Len(nil, 0), "nil does not have length")
|
||||
assert.False(mockAssert.Len(0, 0), "int does not have length")
|
||||
assert.False(mockAssert.Len(true, 0), "true does not have length")
|
||||
assert.False(mockAssert.Len(false, 0), "false does not have length")
|
||||
assert.False(mockAssert.Len('A', 0), "Rune does not have length")
|
||||
assert.False(mockAssert.Len(struct{}{}, 0), "Struct does not have length")
|
||||
|
||||
ch := make(chan int, 5)
|
||||
ch <- 1
|
||||
ch <- 2
|
||||
ch <- 3
|
||||
|
||||
cases := []struct {
|
||||
v interface{}
|
||||
l int
|
||||
}{
|
||||
{[]int{1, 2, 3}, 3},
|
||||
{[...]int{1, 2, 3}, 3},
|
||||
{"ABC", 3},
|
||||
{map[int]int{1: 2, 2: 4, 3: 6}, 3},
|
||||
{ch, 3},
|
||||
|
||||
{[]int{}, 0},
|
||||
{map[int]int{}, 0},
|
||||
{make(chan int), 0},
|
||||
|
||||
{[]int(nil), 0},
|
||||
{map[int]int(nil), 0},
|
||||
{(chan int)(nil), 0},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
assert.True(mockAssert.Len(c.v, c.l), "%#v have %d items", c.v, c.l)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithinDurationWrapper(t *testing.T) {
|
||||
assert := New(t)
|
||||
mockAssert := New(new(testing.T))
|
||||
a := time.Now()
|
||||
b := a.Add(10 * time.Second)
|
||||
|
||||
assert.True(mockAssert.WithinDuration(a, b, 10*time.Second), "A 10s difference is within a 10s time difference")
|
||||
assert.True(mockAssert.WithinDuration(b, a, 10*time.Second), "A 10s difference is within a 10s time difference")
|
||||
|
||||
assert.False(mockAssert.WithinDuration(a, b, 9*time.Second), "A 10s difference is not within a 9s time difference")
|
||||
assert.False(mockAssert.WithinDuration(b, a, 9*time.Second), "A 10s difference is not within a 9s time difference")
|
||||
|
||||
assert.False(mockAssert.WithinDuration(a, b, -9*time.Second), "A 10s difference is not within a 9s time difference")
|
||||
assert.False(mockAssert.WithinDuration(b, a, -9*time.Second), "A 10s difference is not within a 9s time difference")
|
||||
|
||||
assert.False(mockAssert.WithinDuration(a, b, -11*time.Second), "A 10s difference is not within a 9s time difference")
|
||||
assert.False(mockAssert.WithinDuration(b, a, -11*time.Second), "A 10s difference is not within a 9s time difference")
|
||||
}
|
||||
|
||||
func TestInDeltaWrapper(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
|
||||
True(t, assert.InDelta(1.001, 1, 0.01), "|1.001 - 1| <= 0.01")
|
||||
True(t, assert.InDelta(1, 1.001, 0.01), "|1 - 1.001| <= 0.01")
|
||||
True(t, assert.InDelta(1, 2, 1), "|1 - 2| <= 1")
|
||||
False(t, assert.InDelta(1, 2, 0.5), "Expected |1 - 2| <= 0.5 to fail")
|
||||
False(t, assert.InDelta(2, 1, 0.5), "Expected |2 - 1| <= 0.5 to fail")
|
||||
False(t, assert.InDelta("", nil, 1), "Expected non numerals to fail")
|
||||
|
||||
cases := []struct {
|
||||
a, b interface{}
|
||||
delta float64
|
||||
}{
|
||||
{uint8(2), uint8(1), 1},
|
||||
{uint16(2), uint16(1), 1},
|
||||
{uint32(2), uint32(1), 1},
|
||||
{uint64(2), uint64(1), 1},
|
||||
|
||||
{int(2), int(1), 1},
|
||||
{int8(2), int8(1), 1},
|
||||
{int16(2), int16(1), 1},
|
||||
{int32(2), int32(1), 1},
|
||||
{int64(2), int64(1), 1},
|
||||
|
||||
{float32(2), float32(1), 1},
|
||||
{float64(2), float64(1), 1},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
True(t, assert.InDelta(tc.a, tc.b, tc.delta), "Expected |%V - %V| <= %v", tc.a, tc.b, tc.delta)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInEpsilonWrapper(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
|
||||
cases := []struct {
|
||||
a, b interface{}
|
||||
epsilon float64
|
||||
}{
|
||||
{uint8(2), uint16(2), .001},
|
||||
{2.1, 2.2, 0.1},
|
||||
{2.2, 2.1, 0.1},
|
||||
{-2.1, -2.2, 0.1},
|
||||
{-2.2, -2.1, 0.1},
|
||||
{uint64(100), uint8(101), 0.01},
|
||||
{0.1, -0.1, 2},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
True(t, assert.InEpsilon(tc.a, tc.b, tc.epsilon, "Expected %V and %V to have a relative difference of %v", tc.a, tc.b, tc.epsilon))
|
||||
}
|
||||
|
||||
cases = []struct {
|
||||
a, b interface{}
|
||||
epsilon float64
|
||||
}{
|
||||
{uint8(2), int16(-2), .001},
|
||||
{uint64(100), uint8(102), 0.01},
|
||||
{2.1, 2.2, 0.001},
|
||||
{2.2, 2.1, 0.001},
|
||||
{2.1, -2.2, 1},
|
||||
{2.1, "bla-bla", 0},
|
||||
{0.1, -0.1, 1.99},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
False(t, assert.InEpsilon(tc.a, tc.b, tc.epsilon, "Expected %V and %V to have a relative difference of %v", tc.a, tc.b, tc.epsilon))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegexpWrapper(t *testing.T) {
|
||||
|
||||
assert := New(new(testing.T))
|
||||
|
||||
cases := []struct {
|
||||
rx, str string
|
||||
}{
|
||||
{"^start", "start of the line"},
|
||||
{"end$", "in the end"},
|
||||
{"[0-9]{3}[.-]?[0-9]{2}[.-]?[0-9]{2}", "My phone number is 650.12.34"},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
True(t, assert.Regexp(tc.rx, tc.str))
|
||||
True(t, assert.Regexp(regexp.MustCompile(tc.rx), tc.str))
|
||||
False(t, assert.NotRegexp(tc.rx, tc.str))
|
||||
False(t, assert.NotRegexp(regexp.MustCompile(tc.rx), tc.str))
|
||||
}
|
||||
|
||||
cases = []struct {
|
||||
rx, str string
|
||||
}{
|
||||
{"^asdfastart", "Not the start of the line"},
|
||||
{"end$", "in the end."},
|
||||
{"[0-9]{3}[.-]?[0-9]{2}[.-]?[0-9]{2}", "My phone number is 650.12a.34"},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
False(t, assert.Regexp(tc.rx, tc.str), "Expected \"%s\" to not match \"%s\"", tc.rx, tc.str)
|
||||
False(t, assert.Regexp(regexp.MustCompile(tc.rx), tc.str))
|
||||
True(t, assert.NotRegexp(tc.rx, tc.str))
|
||||
True(t, assert.NotRegexp(regexp.MustCompile(tc.rx), tc.str))
|
||||
}
|
||||
}
|
||||
|
||||
func TestZeroWrapper(t *testing.T) {
|
||||
assert := New(t)
|
||||
mockAssert := New(new(testing.T))
|
||||
|
||||
for _, test := range zeros {
|
||||
assert.True(mockAssert.Zero(test), "Zero should return true for %v", test)
|
||||
}
|
||||
|
||||
for _, test := range nonZeros {
|
||||
assert.False(mockAssert.Zero(test), "Zero should return false for %v", test)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotZeroWrapper(t *testing.T) {
|
||||
assert := New(t)
|
||||
mockAssert := New(new(testing.T))
|
||||
|
||||
for _, test := range zeros {
|
||||
assert.False(mockAssert.NotZero(test), "Zero should return true for %v", test)
|
||||
}
|
||||
|
||||
for _, test := range nonZeros {
|
||||
assert.True(mockAssert.NotZero(test), "Zero should return false for %v", test)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONEqWrapper_EqualSONString(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
if !assert.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"hello": "world", "foo": "bar"}`) {
|
||||
t.Error("JSONEq should return true")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestJSONEqWrapper_EquivalentButNotEqual(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
if !assert.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) {
|
||||
t.Error("JSONEq should return true")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestJSONEqWrapper_HashOfArraysAndHashes(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
if !assert.JSONEq("{\r\n\t\"numeric\": 1.5,\r\n\t\"array\": [{\"foo\": \"bar\"}, 1, \"string\", [\"nested\", \"array\", 5.5]],\r\n\t\"hash\": {\"nested\": \"hash\", \"nested_slice\": [\"this\", \"is\", \"nested\"]},\r\n\t\"string\": \"foo\"\r\n}",
|
||||
"{\r\n\t\"numeric\": 1.5,\r\n\t\"hash\": {\"nested\": \"hash\", \"nested_slice\": [\"this\", \"is\", \"nested\"]},\r\n\t\"string\": \"foo\",\r\n\t\"array\": [{\"foo\": \"bar\"}, 1, \"string\", [\"nested\", \"array\", 5.5]]\r\n}") {
|
||||
t.Error("JSONEq should return true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONEqWrapper_Array(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
if !assert.JSONEq(`["foo", {"hello": "world", "nested": "hash"}]`, `["foo", {"nested": "hash", "hello": "world"}]`) {
|
||||
t.Error("JSONEq should return true")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestJSONEqWrapper_HashAndArrayNotEquivalent(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
if assert.JSONEq(`["foo", {"hello": "world", "nested": "hash"}]`, `{"foo": "bar", {"nested": "hash", "hello": "world"}}`) {
|
||||
t.Error("JSONEq should return false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONEqWrapper_HashesNotEquivalent(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
if assert.JSONEq(`{"foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) {
|
||||
t.Error("JSONEq should return false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONEqWrapper_ActualIsNotJSON(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
if assert.JSONEq(`{"foo": "bar"}`, "Not JSON") {
|
||||
t.Error("JSONEq should return false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONEqWrapper_ExpectedIsNotJSON(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
if assert.JSONEq("Not JSON", `{"foo": "bar", "hello": "world"}`) {
|
||||
t.Error("JSONEq should return false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONEqWrapper_ExpectedAndActualNotJSON(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
if assert.JSONEq("Not JSON", "Not JSON") {
|
||||
t.Error("JSONEq should return false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONEqWrapper_ArraysOfDifferentOrder(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
if assert.JSONEq(`["foo", {"hello": "world", "nested": "hash"}]`, `[{ "hello": "world", "nested": "hash"}, "foo"]`) {
|
||||
t.Error("JSONEq should return false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestYAMLEqWrapper_EqualYAMLString(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
if !assert.YAMLEq(`{"hello": "world", "foo": "bar"}`, `{"hello": "world", "foo": "bar"}`) {
|
||||
t.Error("YAMLEq should return true")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestYAMLEqWrapper_EquivalentButNotEqual(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
if !assert.YAMLEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) {
|
||||
t.Error("YAMLEq should return true")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestYAMLEqWrapper_HashOfArraysAndHashes(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
expected := `
|
||||
numeric: 1.5
|
||||
array:
|
||||
- foo: bar
|
||||
- 1
|
||||
- "string"
|
||||
- ["nested", "array", 5.5]
|
||||
hash:
|
||||
nested: hash
|
||||
nested_slice: [this, is, nested]
|
||||
string: "foo"
|
||||
`
|
||||
|
||||
actual := `
|
||||
numeric: 1.5
|
||||
hash:
|
||||
nested: hash
|
||||
nested_slice: [this, is, nested]
|
||||
string: "foo"
|
||||
array:
|
||||
- foo: bar
|
||||
- 1
|
||||
- "string"
|
||||
- ["nested", "array", 5.5]
|
||||
`
|
||||
if !assert.YAMLEq(expected, actual) {
|
||||
t.Error("YAMLEq should return true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestYAMLEqWrapper_Array(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
if !assert.YAMLEq(`["foo", {"hello": "world", "nested": "hash"}]`, `["foo", {"nested": "hash", "hello": "world"}]`) {
|
||||
t.Error("YAMLEq should return true")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestYAMLEqWrapper_HashAndArrayNotEquivalent(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
if assert.YAMLEq(`["foo", {"hello": "world", "nested": "hash"}]`, `{"foo": "bar", {"nested": "hash", "hello": "world"}}`) {
|
||||
t.Error("YAMLEq should return false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestYAMLEqWrapper_HashesNotEquivalent(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
if assert.YAMLEq(`{"foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) {
|
||||
t.Error("YAMLEq should return false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestYAMLEqWrapper_ActualIsSimpleString(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
if assert.YAMLEq(`{"foo": "bar"}`, "Simple String") {
|
||||
t.Error("YAMLEq should return false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestYAMLEqWrapper_ExpectedIsSimpleString(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
if assert.YAMLEq("Simple String", `{"foo": "bar", "hello": "world"}`) {
|
||||
t.Error("YAMLEq should return false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestYAMLEqWrapper_ExpectedAndActualSimpleString(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
if !assert.YAMLEq("Simple String", "Simple String") {
|
||||
t.Error("YAMLEq should return true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestYAMLEqWrapper_ArraysOfDifferentOrder(t *testing.T) {
|
||||
assert := New(new(testing.T))
|
||||
if assert.YAMLEq(`["foo", {"hello": "world", "nested": "hash"}]`, `[{ "hello": "world", "nested": "hash"}, "foo"]`) {
|
||||
t.Error("YAMLEq should return false")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
package assert
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// httpCode is a helper that returns HTTP code of the response. It returns -1 and
|
||||
// an error if building a new request fails.
|
||||
func httpCode(handler http.HandlerFunc, method, url string, values url.Values) (int, error) {
|
||||
w := httptest.NewRecorder()
|
||||
req, err := http.NewRequest(method, url, nil)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
req.URL.RawQuery = values.Encode()
|
||||
handler(w, req)
|
||||
return w.Code, nil
|
||||
}
|
||||
|
||||
// HTTPSuccess asserts that a specified handler returns a success status code.
|
||||
//
|
||||
// assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil)
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
code, err := httpCode(handler, method, url, values)
|
||||
if err != nil {
|
||||
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
|
||||
return false
|
||||
}
|
||||
|
||||
isSuccessCode := code >= http.StatusOK && code <= http.StatusPartialContent
|
||||
if !isSuccessCode {
|
||||
Fail(t, fmt.Sprintf("Expected HTTP success status code for %q but received %d", url+"?"+values.Encode(), code))
|
||||
}
|
||||
|
||||
return isSuccessCode
|
||||
}
|
||||
|
||||
// HTTPRedirect asserts that a specified handler returns a redirect status code.
|
||||
//
|
||||
// assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
code, err := httpCode(handler, method, url, values)
|
||||
if err != nil {
|
||||
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
|
||||
return false
|
||||
}
|
||||
|
||||
isRedirectCode := code >= http.StatusMultipleChoices && code <= http.StatusTemporaryRedirect
|
||||
if !isRedirectCode {
|
||||
Fail(t, fmt.Sprintf("Expected HTTP redirect status code for %q but received %d", url+"?"+values.Encode(), code))
|
||||
}
|
||||
|
||||
return isRedirectCode
|
||||
}
|
||||
|
||||
// HTTPError asserts that a specified handler returns an error status code.
|
||||
//
|
||||
// assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
code, err := httpCode(handler, method, url, values)
|
||||
if err != nil {
|
||||
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
|
||||
return false
|
||||
}
|
||||
|
||||
isErrorCode := code >= http.StatusBadRequest
|
||||
if !isErrorCode {
|
||||
Fail(t, fmt.Sprintf("Expected HTTP error status code for %q but received %d", url+"?"+values.Encode(), code))
|
||||
}
|
||||
|
||||
return isErrorCode
|
||||
}
|
||||
|
||||
// HTTPBody is a helper that returns HTTP body of the response. It returns
|
||||
// empty string if building a new request fails.
|
||||
func HTTPBody(handler http.HandlerFunc, method, url string, values url.Values) string {
|
||||
w := httptest.NewRecorder()
|
||||
req, err := http.NewRequest(method, url+"?"+values.Encode(), nil)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
handler(w, req)
|
||||
return w.Body.String()
|
||||
}
|
||||
|
||||
// HTTPBodyContains asserts that a specified handler returns a
|
||||
// body that contains a string.
|
||||
//
|
||||
// assert.HTTPBodyContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
body := HTTPBody(handler, method, url, values)
|
||||
|
||||
contains := strings.Contains(body, fmt.Sprint(str))
|
||||
if !contains {
|
||||
Fail(t, fmt.Sprintf("Expected response body for \"%s\" to contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body))
|
||||
}
|
||||
|
||||
return contains
|
||||
}
|
||||
|
||||
// HTTPBodyNotContains asserts that a specified handler returns a
|
||||
// body that does not contain a string.
|
||||
//
|
||||
// assert.HTTPBodyNotContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
body := HTTPBody(handler, method, url, values)
|
||||
|
||||
contains := strings.Contains(body, fmt.Sprint(str))
|
||||
if contains {
|
||||
Fail(t, fmt.Sprintf("Expected response body for \"%s\" to NOT contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body))
|
||||
}
|
||||
|
||||
return !contains
|
||||
}
|
146
vendor/github.com/stretchr/testify/assert/http_assertions_test.go
generated
vendored
Normal file
146
vendor/github.com/stretchr/testify/assert/http_assertions_test.go
generated
vendored
Normal file
|
@ -0,0 +1,146 @@
|
|||
package assert
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func httpOK(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func httpRedirect(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusTemporaryRedirect)
|
||||
}
|
||||
|
||||
func httpError(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
func TestHTTPSuccess(t *testing.T) {
|
||||
assert := New(t)
|
||||
|
||||
mockT1 := new(testing.T)
|
||||
assert.Equal(HTTPSuccess(mockT1, httpOK, "GET", "/", nil), true)
|
||||
assert.False(mockT1.Failed())
|
||||
|
||||
mockT2 := new(testing.T)
|
||||
assert.Equal(HTTPSuccess(mockT2, httpRedirect, "GET", "/", nil), false)
|
||||
assert.True(mockT2.Failed())
|
||||
|
||||
mockT3 := new(testing.T)
|
||||
assert.Equal(HTTPSuccess(mockT3, httpError, "GET", "/", nil), false)
|
||||
assert.True(mockT3.Failed())
|
||||
}
|
||||
|
||||
func TestHTTPRedirect(t *testing.T) {
|
||||
assert := New(t)
|
||||
|
||||
mockT1 := new(testing.T)
|
||||
assert.Equal(HTTPRedirect(mockT1, httpOK, "GET", "/", nil), false)
|
||||
assert.True(mockT1.Failed())
|
||||
|
||||
mockT2 := new(testing.T)
|
||||
assert.Equal(HTTPRedirect(mockT2, httpRedirect, "GET", "/", nil), true)
|
||||
assert.False(mockT2.Failed())
|
||||
|
||||
mockT3 := new(testing.T)
|
||||
assert.Equal(HTTPRedirect(mockT3, httpError, "GET", "/", nil), false)
|
||||
assert.True(mockT3.Failed())
|
||||
}
|
||||
|
||||
func TestHTTPError(t *testing.T) {
|
||||
assert := New(t)
|
||||
|
||||
mockT1 := new(testing.T)
|
||||
assert.Equal(HTTPError(mockT1, httpOK, "GET", "/", nil), false)
|
||||
assert.True(mockT1.Failed())
|
||||
|
||||
mockT2 := new(testing.T)
|
||||
assert.Equal(HTTPError(mockT2, httpRedirect, "GET", "/", nil), false)
|
||||
assert.True(mockT2.Failed())
|
||||
|
||||
mockT3 := new(testing.T)
|
||||
assert.Equal(HTTPError(mockT3, httpError, "GET", "/", nil), true)
|
||||
assert.False(mockT3.Failed())
|
||||
}
|
||||
|
||||
func TestHTTPStatusesWrapper(t *testing.T) {
|
||||
assert := New(t)
|
||||
mockAssert := New(new(testing.T))
|
||||
|
||||
assert.Equal(mockAssert.HTTPSuccess(httpOK, "GET", "/", nil), true)
|
||||
assert.Equal(mockAssert.HTTPSuccess(httpRedirect, "GET", "/", nil), false)
|
||||
assert.Equal(mockAssert.HTTPSuccess(httpError, "GET", "/", nil), false)
|
||||
|
||||
assert.Equal(mockAssert.HTTPRedirect(httpOK, "GET", "/", nil), false)
|
||||
assert.Equal(mockAssert.HTTPRedirect(httpRedirect, "GET", "/", nil), true)
|
||||
assert.Equal(mockAssert.HTTPRedirect(httpError, "GET", "/", nil), false)
|
||||
|
||||
assert.Equal(mockAssert.HTTPError(httpOK, "GET", "/", nil), false)
|
||||
assert.Equal(mockAssert.HTTPError(httpRedirect, "GET", "/", nil), false)
|
||||
assert.Equal(mockAssert.HTTPError(httpError, "GET", "/", nil), true)
|
||||
}
|
||||
|
||||
func httpHelloName(w http.ResponseWriter, r *http.Request) {
|
||||
name := r.FormValue("name")
|
||||
w.Write([]byte(fmt.Sprintf("Hello, %s!", name)))
|
||||
}
|
||||
|
||||
func TestHTTPRequestWithNoParams(t *testing.T) {
|
||||
var got *http.Request
|
||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||
got = r
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
True(t, HTTPSuccess(t, handler, "GET", "/url", nil))
|
||||
|
||||
Empty(t, got.URL.Query())
|
||||
Equal(t, "/url", got.URL.RequestURI())
|
||||
}
|
||||
|
||||
func TestHTTPRequestWithParams(t *testing.T) {
|
||||
var got *http.Request
|
||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||
got = r
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
params := url.Values{}
|
||||
params.Add("id", "12345")
|
||||
|
||||
True(t, HTTPSuccess(t, handler, "GET", "/url", params))
|
||||
|
||||
Equal(t, url.Values{"id": []string{"12345"}}, got.URL.Query())
|
||||
Equal(t, "/url?id=12345", got.URL.String())
|
||||
Equal(t, "/url?id=12345", got.URL.RequestURI())
|
||||
}
|
||||
|
||||
func TestHttpBody(t *testing.T) {
|
||||
assert := New(t)
|
||||
mockT := new(testing.T)
|
||||
|
||||
assert.True(HTTPBodyContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "Hello, World!"))
|
||||
assert.True(HTTPBodyContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "World"))
|
||||
assert.False(HTTPBodyContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "world"))
|
||||
|
||||
assert.False(HTTPBodyNotContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "Hello, World!"))
|
||||
assert.False(HTTPBodyNotContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "World"))
|
||||
assert.True(HTTPBodyNotContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "world"))
|
||||
}
|
||||
|
||||
func TestHttpBodyWrappers(t *testing.T) {
|
||||
assert := New(t)
|
||||
mockAssert := New(new(testing.T))
|
||||
|
||||
assert.True(mockAssert.HTTPBodyContains(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "Hello, World!"))
|
||||
assert.True(mockAssert.HTTPBodyContains(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "World"))
|
||||
assert.False(mockAssert.HTTPBodyContains(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "world"))
|
||||
|
||||
assert.False(mockAssert.HTTPBodyNotContains(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "Hello, World!"))
|
||||
assert.False(mockAssert.HTTPBodyNotContains(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "World"))
|
||||
assert.True(mockAssert.HTTPBodyNotContains(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "world"))
|
||||
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
Copyright (c) 2015 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,22 @@
|
|||
Additional IP Rights Grant (Patents)
|
||||
|
||||
"This implementation" means the copyrightable works distributed by
|
||||
Google as part of the Go project.
|
||||
|
||||
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||
patent license to make, have made, use, offer to sell, sell, import,
|
||||
transfer and otherwise run, modify and propagate the contents of this
|
||||
implementation of Go, where such license applies only to those patent
|
||||
claims, both currently owned or controlled by Google and acquired in
|
||||
the future, licensable by Google that are necessarily infringed by this
|
||||
implementation of Go. This grant does not include claims that would be
|
||||
infringed only as a consequence of further modification of this
|
||||
implementation. If you or your agent or exclusive licensee institute or
|
||||
order or agree to the institution of patent litigation against any
|
||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||
that this implementation of Go or any code incorporated within this
|
||||
implementation of Go constitutes direct or contributory patent
|
||||
infringement, or inducement of patent infringement, then any patent
|
||||
rights granted to you under this License for this implementation of Go
|
||||
shall terminate as of the date such litigation is filed.
|
|
@ -0,0 +1,3 @@
|
|||
tables.go: ../x86map/map.go ../x86.csv
|
||||
go run ../x86map/map.go -fmt=decoder ../x86.csv >_tables.go && gofmt _tables.go >tables.go && rm _tables.go
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,85 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package x86asm
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDecode(t *testing.T) {
|
||||
data, err := ioutil.ReadFile("testdata/decode.txt")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
all := string(data)
|
||||
for strings.Contains(all, "\t\t") {
|
||||
all = strings.Replace(all, "\t\t", "\t", -1)
|
||||
}
|
||||
for _, line := range strings.Split(all, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
f := strings.SplitN(line, "\t", 4)
|
||||
i := strings.Index(f[0], "|")
|
||||
if i < 0 {
|
||||
t.Errorf("parsing %q: missing | separator", f[0])
|
||||
continue
|
||||
}
|
||||
if i%2 != 0 {
|
||||
t.Errorf("parsing %q: misaligned | separator", f[0])
|
||||
}
|
||||
size := i / 2
|
||||
code, err := hex.DecodeString(f[0][:i] + f[0][i+1:])
|
||||
if err != nil {
|
||||
t.Errorf("parsing %q: %v", f[0], err)
|
||||
continue
|
||||
}
|
||||
mode, err := strconv.Atoi(f[1])
|
||||
if err != nil {
|
||||
t.Errorf("invalid mode %q in: %s", f[1], line)
|
||||
continue
|
||||
}
|
||||
syntax, asm := f[2], f[3]
|
||||
inst, err := Decode(code, mode)
|
||||
var out string
|
||||
if err != nil {
|
||||
out = "error: " + err.Error()
|
||||
} else {
|
||||
switch syntax {
|
||||
case "gnu":
|
||||
out = GNUSyntax(inst, 0, nil)
|
||||
case "intel":
|
||||
out = IntelSyntax(inst, 0, nil)
|
||||
case "plan9": // [sic]
|
||||
out = GoSyntax(inst, 0, nil)
|
||||
default:
|
||||
t.Errorf("unknown syntax %q", syntax)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if out != asm || inst.Len != size {
|
||||
t.Errorf("Decode(%s) [%s] = %s, %d, want %s, %d", f[0], syntax, out, inst.Len, asm, size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeDoesNotCrash(t *testing.T) {
|
||||
cases := [...][]byte{
|
||||
[]byte{},
|
||||
[]byte{0xc5},
|
||||
[]byte{0xc4},
|
||||
}
|
||||
for _, test := range cases {
|
||||
_, err := Decode([]byte(test), 64) // the only goal is that this line does not panic
|
||||
if err == nil {
|
||||
t.Errorf("expected error on invalid instruction %x", test)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,816 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Support for testing against external disassembler program.
|
||||
|
||||
package x86asm
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/rand"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
printTests = flag.Bool("printtests", false, "print test cases that exercise new code paths")
|
||||
dumpTest = flag.Bool("dump", false, "dump all encodings")
|
||||
mismatch = flag.Bool("mismatch", false, "log allowed mismatches")
|
||||
longTest = flag.Bool("long", false, "long test")
|
||||
keep = flag.Bool("keep", false, "keep object files around")
|
||||
debug = false
|
||||
)
|
||||
|
||||
// An ExtInst represents a single decoded instruction parsed
|
||||
// from an external disassembler's output.
|
||||
type ExtInst struct {
|
||||
addr uint32
|
||||
enc [32]byte
|
||||
nenc int
|
||||
text string
|
||||
}
|
||||
|
||||
func (r ExtInst) String() string {
|
||||
return fmt.Sprintf("%#x: % x: %s", r.addr, r.enc, r.text)
|
||||
}
|
||||
|
||||
// An ExtDis is a connection between an external disassembler and a test.
|
||||
type ExtDis struct {
|
||||
Arch int
|
||||
Dec chan ExtInst
|
||||
File *os.File
|
||||
Size int
|
||||
KeepFile bool
|
||||
Cmd *exec.Cmd
|
||||
}
|
||||
|
||||
// Run runs the given command - the external disassembler - and returns
|
||||
// a buffered reader of its standard output.
|
||||
func (ext *ExtDis) Run(cmd ...string) (*bufio.Reader, error) {
|
||||
if *keep {
|
||||
log.Printf("%s\n", strings.Join(cmd, " "))
|
||||
}
|
||||
ext.Cmd = exec.Command(cmd[0], cmd[1:]...)
|
||||
out, err := ext.Cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("stdoutpipe: %v", err)
|
||||
}
|
||||
if err := ext.Cmd.Start(); err != nil {
|
||||
return nil, fmt.Errorf("exec: %v", err)
|
||||
}
|
||||
|
||||
b := bufio.NewReaderSize(out, 1<<20)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// Wait waits for the command started with Run to exit.
|
||||
func (ext *ExtDis) Wait() error {
|
||||
return ext.Cmd.Wait()
|
||||
}
|
||||
|
||||
// testExtDis tests a set of byte sequences against an external disassembler.
|
||||
// The disassembler is expected to produce the given syntax and be run
|
||||
// in the given architecture mode (16, 32, or 64-bit).
|
||||
// The extdis function must start the external disassembler
|
||||
// and then parse its output, sending the parsed instructions on ext.Dec.
|
||||
// The generate function calls its argument f once for each byte sequence
|
||||
// to be tested. The generate function itself will be called twice, and it must
|
||||
// make the same sequence of calls to f each time.
|
||||
// When a disassembly does not match the internal decoding,
|
||||
// allowedMismatch determines whether this mismatch should be
|
||||
// allowed, or else considered an error.
|
||||
func testExtDis(
|
||||
t *testing.T,
|
||||
syntax string,
|
||||
arch int,
|
||||
extdis func(ext *ExtDis) error,
|
||||
generate func(f func([]byte)),
|
||||
allowedMismatch func(text string, size int, inst *Inst, dec ExtInst) bool,
|
||||
) {
|
||||
decoderCover = make([]bool, len(decoder))
|
||||
defer func() {
|
||||
decoderCover = nil
|
||||
}()
|
||||
|
||||
start := time.Now()
|
||||
ext := &ExtDis{
|
||||
Dec: make(chan ExtInst),
|
||||
Arch: arch,
|
||||
}
|
||||
errc := make(chan error)
|
||||
|
||||
// First pass: write instructions to input file for external disassembler.
|
||||
file, f, size, err := writeInst(generate)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ext.Size = size
|
||||
ext.File = f
|
||||
defer func() {
|
||||
f.Close()
|
||||
if !*keep {
|
||||
os.Remove(file)
|
||||
}
|
||||
}()
|
||||
|
||||
// Second pass: compare disassembly against our decodings.
|
||||
var (
|
||||
totalTests = 0
|
||||
totalSkips = 0
|
||||
totalErrors = 0
|
||||
|
||||
errors = make([]string, 0, 100) // sampled errors, at most cap
|
||||
)
|
||||
go func() {
|
||||
errc <- extdis(ext)
|
||||
}()
|
||||
generate(func(enc []byte) {
|
||||
dec, ok := <-ext.Dec
|
||||
if !ok {
|
||||
t.Errorf("decoding stream ended early")
|
||||
return
|
||||
}
|
||||
inst, text := disasm(syntax, arch, pad(enc))
|
||||
totalTests++
|
||||
if *dumpTest {
|
||||
fmt.Printf("%x -> %s [%d]\n", enc[:len(enc)], dec.text, dec.nenc)
|
||||
}
|
||||
if text != dec.text || inst.Len != dec.nenc {
|
||||
suffix := ""
|
||||
if allowedMismatch(text, size, &inst, dec) {
|
||||
totalSkips++
|
||||
if !*mismatch {
|
||||
return
|
||||
}
|
||||
suffix += " (allowed mismatch)"
|
||||
}
|
||||
totalErrors++
|
||||
if len(errors) >= cap(errors) {
|
||||
j := rand.Intn(totalErrors)
|
||||
if j >= cap(errors) {
|
||||
return
|
||||
}
|
||||
errors = append(errors[:j], errors[j+1:]...)
|
||||
}
|
||||
errors = append(errors, fmt.Sprintf("decode(%x) = %q, %d, want %q, %d%s", enc, text, inst.Len, dec.text, dec.nenc, suffix))
|
||||
}
|
||||
})
|
||||
|
||||
if *mismatch {
|
||||
totalErrors -= totalSkips
|
||||
}
|
||||
|
||||
for _, b := range errors {
|
||||
t.Log(b)
|
||||
}
|
||||
|
||||
if totalErrors > 0 {
|
||||
t.Fail()
|
||||
}
|
||||
t.Logf("%d test cases, %d expected mismatches, %d failures; %.0f cases/second", totalTests, totalSkips, totalErrors, float64(totalTests)/time.Since(start).Seconds())
|
||||
|
||||
if err := <-errc; err != nil {
|
||||
t.Fatalf("external disassembler: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
const start = 0x8000 // start address of text
|
||||
|
||||
// writeInst writes the generated byte sequences to a new file
|
||||
// starting at offset start. That file is intended to be the input to
|
||||
// the external disassembler.
|
||||
func writeInst(generate func(func([]byte))) (file string, f *os.File, size int, err error) {
|
||||
f, err = ioutil.TempFile("", "x86map")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
file = f.Name()
|
||||
|
||||
f.Seek(start, io.SeekStart)
|
||||
w := bufio.NewWriter(f)
|
||||
defer w.Flush()
|
||||
size = 0
|
||||
generate(func(x []byte) {
|
||||
if len(x) > 16 {
|
||||
x = x[:16]
|
||||
}
|
||||
if debug {
|
||||
fmt.Printf("%#x: %x%x\n", start+size, x, pops[len(x):])
|
||||
}
|
||||
w.Write(x)
|
||||
w.Write(pops[len(x):])
|
||||
size += len(pops)
|
||||
})
|
||||
return file, f, size, nil
|
||||
}
|
||||
|
||||
// 0x5F is a single-byte pop instruction.
|
||||
// We pad the bytes we want decoded with enough 0x5Fs
|
||||
// that no matter what state the instruction stream is in
|
||||
// after reading our bytes, the pops will get us back to
|
||||
// a forced instruction boundary.
|
||||
var pops = []byte{
|
||||
0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f,
|
||||
0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f,
|
||||
0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f,
|
||||
0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f,
|
||||
}
|
||||
|
||||
// pad pads the code sequence with pops.
|
||||
func pad(enc []byte) []byte {
|
||||
return append(enc[:len(enc):len(enc)], pops...)
|
||||
}
|
||||
|
||||
// disasm returns the decoded instruction and text
|
||||
// for the given source bytes, using the given syntax and mode.
|
||||
func disasm(syntax string, mode int, src []byte) (inst Inst, text string) {
|
||||
// If printTests is set, we record the coverage value
|
||||
// before and after, and we write out the inputs for which
|
||||
// coverage went up, in the format expected in testdata/decode.text.
|
||||
// This produces a fairly small set of test cases that exercise nearly
|
||||
// all the code.
|
||||
var cover float64
|
||||
if *printTests {
|
||||
cover -= coverage()
|
||||
}
|
||||
|
||||
inst, err := decode1(src, mode, syntax == "gnu")
|
||||
if err != nil {
|
||||
text = "error: " + err.Error()
|
||||
} else {
|
||||
switch syntax {
|
||||
case "gnu":
|
||||
text = GNUSyntax(inst, 0, nil)
|
||||
case "intel":
|
||||
text = IntelSyntax(inst, 0, nil)
|
||||
case "plan9": // [sic]
|
||||
text = GoSyntax(inst, 0, nil)
|
||||
default:
|
||||
text = "error: unknown syntax " + syntax
|
||||
}
|
||||
}
|
||||
|
||||
if *printTests {
|
||||
cover += coverage()
|
||||
if cover > 0 {
|
||||
max := len(src)
|
||||
if max > 16 && inst.Len <= 16 {
|
||||
max = 16
|
||||
}
|
||||
fmt.Printf("%x|%x\t%d\t%s\t%s\n", src[:inst.Len], src[inst.Len:max], mode, syntax, text)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// coverage returns a floating point number denoting the
|
||||
// test coverage until now. The number increases when new code paths are exercised,
|
||||
// both in the Go program and in the decoder byte code.
|
||||
func coverage() float64 {
|
||||
/*
|
||||
testing.Coverage is not in the main distribution.
|
||||
The implementation, which must go in package testing, is:
|
||||
|
||||
// Coverage reports the current code coverage as a fraction in the range [0, 1].
|
||||
func Coverage() float64 {
|
||||
var n, d int64
|
||||
for _, counters := range cover.Counters {
|
||||
for _, c := range counters {
|
||||
if c > 0 {
|
||||
n++
|
||||
}
|
||||
d++
|
||||
}
|
||||
}
|
||||
if d == 0 {
|
||||
return 0
|
||||
}
|
||||
return float64(n) / float64(d)
|
||||
}
|
||||
*/
|
||||
|
||||
var f float64
|
||||
// f += testing.Coverage()
|
||||
f += decodeCoverage()
|
||||
return f
|
||||
}
|
||||
|
||||
func decodeCoverage() float64 {
|
||||
n := 0
|
||||
for _, t := range decoderCover {
|
||||
if t {
|
||||
n++
|
||||
}
|
||||
}
|
||||
return float64(1+n) / float64(1+len(decoderCover))
|
||||
}
|
||||
|
||||
// Helpers for writing disassembler output parsers.
|
||||
|
||||
// isPrefix reports whether text is the name of an instruction prefix.
|
||||
func isPrefix(text string) bool {
|
||||
return prefixByte[text] > 0
|
||||
}
|
||||
|
||||
// prefixByte maps instruction prefix text to actual prefix byte values.
|
||||
var prefixByte = map[string]byte{
|
||||
"es": 0x26,
|
||||
"cs": 0x2e,
|
||||
"ss": 0x36,
|
||||
"ds": 0x3e,
|
||||
"fs": 0x64,
|
||||
"gs": 0x65,
|
||||
"data16": 0x66,
|
||||
"addr16": 0x67,
|
||||
"lock": 0xf0,
|
||||
"repn": 0xf2,
|
||||
"repne": 0xf2,
|
||||
"rep": 0xf3,
|
||||
"repe": 0xf3,
|
||||
"xacquire": 0xf2,
|
||||
"xrelease": 0xf3,
|
||||
"bnd": 0xf2,
|
||||
"addr32": 0x66,
|
||||
"data32": 0x67,
|
||||
}
|
||||
|
||||
// hasPrefix reports whether any of the space-separated words in the text s
|
||||
// begins with any of the given prefixes.
|
||||
func hasPrefix(s string, prefixes ...string) bool {
|
||||
for _, prefix := range prefixes {
|
||||
for s := s; s != ""; {
|
||||
if strings.HasPrefix(s, prefix) {
|
||||
return true
|
||||
}
|
||||
i := strings.Index(s, " ")
|
||||
if i < 0 {
|
||||
break
|
||||
}
|
||||
s = s[i+1:]
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// contains reports whether the text s contains any of the given substrings.
|
||||
func contains(s string, substrings ...string) bool {
|
||||
for _, sub := range substrings {
|
||||
if strings.Contains(s, sub) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isHex reports whether b is a hexadecimal character (0-9A-Fa-f).
|
||||
func isHex(b byte) bool { return b == '0' || unhex[b] > 0 }
|
||||
|
||||
// parseHex parses the hexadecimal byte dump in hex,
|
||||
// appending the parsed bytes to raw and returning the updated slice.
|
||||
// The returned bool signals whether any invalid hex was found.
|
||||
// Spaces and tabs between bytes are okay but any other non-hex is not.
|
||||
func parseHex(hex []byte, raw []byte) ([]byte, bool) {
|
||||
hex = trimSpace(hex)
|
||||
for j := 0; j < len(hex); {
|
||||
for hex[j] == ' ' || hex[j] == '\t' {
|
||||
j++
|
||||
}
|
||||
if j >= len(hex) {
|
||||
break
|
||||
}
|
||||
if j+2 > len(hex) || !isHex(hex[j]) || !isHex(hex[j+1]) {
|
||||
return nil, false
|
||||
}
|
||||
raw = append(raw, unhex[hex[j]]<<4|unhex[hex[j+1]])
|
||||
j += 2
|
||||
}
|
||||
return raw, true
|
||||
}
|
||||
|
||||
var unhex = [256]byte{
|
||||
'0': 0,
|
||||
'1': 1,
|
||||
'2': 2,
|
||||
'3': 3,
|
||||
'4': 4,
|
||||
'5': 5,
|
||||
'6': 6,
|
||||
'7': 7,
|
||||
'8': 8,
|
||||
'9': 9,
|
||||
'A': 10,
|
||||
'B': 11,
|
||||
'C': 12,
|
||||
'D': 13,
|
||||
'E': 14,
|
||||
'F': 15,
|
||||
'a': 10,
|
||||
'b': 11,
|
||||
'c': 12,
|
||||
'd': 13,
|
||||
'e': 14,
|
||||
'f': 15,
|
||||
}
|
||||
|
||||
// index is like bytes.Index(s, []byte(t)) but avoids the allocation.
|
||||
func index(s []byte, t string) int {
|
||||
i := 0
|
||||
for {
|
||||
j := bytes.IndexByte(s[i:], t[0])
|
||||
if j < 0 {
|
||||
return -1
|
||||
}
|
||||
i = i + j
|
||||
if i+len(t) > len(s) {
|
||||
return -1
|
||||
}
|
||||
for k := 1; k < len(t); k++ {
|
||||
if s[i+k] != t[k] {
|
||||
goto nomatch
|
||||
}
|
||||
}
|
||||
return i
|
||||
nomatch:
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
// fixSpace rewrites runs of spaces, tabs, and newline characters into single spaces in s.
|
||||
// If s must be rewritten, it is rewritten in place.
|
||||
func fixSpace(s []byte) []byte {
|
||||
s = trimSpace(s)
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] == '\t' || s[i] == '\n' || i > 0 && s[i] == ' ' && s[i-1] == ' ' {
|
||||
goto Fix
|
||||
}
|
||||
}
|
||||
return s
|
||||
|
||||
Fix:
|
||||
b := s
|
||||
w := 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
c := s[i]
|
||||
if c == '\t' || c == '\n' {
|
||||
c = ' '
|
||||
}
|
||||
if c == ' ' && w > 0 && b[w-1] == ' ' {
|
||||
continue
|
||||
}
|
||||
b[w] = c
|
||||
w++
|
||||
}
|
||||
if w > 0 && b[w-1] == ' ' {
|
||||
w--
|
||||
}
|
||||
return b[:w]
|
||||
}
|
||||
|
||||
// trimSpace trims leading and trailing space from s, returning a subslice of s.
|
||||
func trimSpace(s []byte) []byte {
|
||||
j := len(s)
|
||||
for j > 0 && (s[j-1] == ' ' || s[j-1] == '\t' || s[j-1] == '\n') {
|
||||
j--
|
||||
}
|
||||
i := 0
|
||||
for i < j && (s[i] == ' ' || s[i] == '\t') {
|
||||
i++
|
||||
}
|
||||
return s[i:j]
|
||||
}
|
||||
|
||||
// pcrel and pcrelw match instructions using relative addressing mode.
|
||||
var (
|
||||
pcrel = regexp.MustCompile(`^((?:.* )?(?:j[a-z]+|call|ljmp|loopn?e?w?|xbegin)q?(?:,p[nt])?) 0x([0-9a-f]+)$`)
|
||||
pcrelw = regexp.MustCompile(`^((?:.* )?(?:callw|jmpw|xbeginw|ljmpw)(?:,p[nt])?) 0x([0-9a-f]+)$`)
|
||||
)
|
||||
|
||||
// Generators.
|
||||
//
|
||||
// The test cases are described as functions that invoke a callback repeatedly,
|
||||
// with a new input sequence each time. These helpers make writing those
|
||||
// a little easier.
|
||||
|
||||
// hexCases generates the cases written in hexadecimal in the encoded string.
|
||||
// Spaces in 'encoded' separate entire test cases, not individual bytes.
|
||||
func hexCases(t *testing.T, encoded string) func(func([]byte)) {
|
||||
return func(try func([]byte)) {
|
||||
for _, x := range strings.Fields(encoded) {
|
||||
src, err := hex.DecodeString(x)
|
||||
if err != nil {
|
||||
t.Errorf("parsing %q: %v", x, err)
|
||||
}
|
||||
try(src)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// testdataCases generates the test cases recorded in testdata/decode.txt.
|
||||
// It only uses the inputs; it ignores the answers recorded in that file.
|
||||
func testdataCases(t *testing.T) func(func([]byte)) {
|
||||
var codes [][]byte
|
||||
data, err := ioutil.ReadFile("testdata/decode.txt")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
f := strings.Fields(line)[0]
|
||||
i := strings.Index(f, "|")
|
||||
if i < 0 {
|
||||
t.Errorf("parsing %q: missing | separator", f)
|
||||
continue
|
||||
}
|
||||
if i%2 != 0 {
|
||||
t.Errorf("parsing %q: misaligned | separator", f)
|
||||
}
|
||||
code, err := hex.DecodeString(f[:i] + f[i+1:])
|
||||
if err != nil {
|
||||
t.Errorf("parsing %q: %v", f, err)
|
||||
continue
|
||||
}
|
||||
codes = append(codes, code)
|
||||
}
|
||||
|
||||
return func(try func([]byte)) {
|
||||
for _, code := range codes {
|
||||
try(code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// manyPrefixes generates all possible 2⁹ combinations of nine chosen prefixes.
|
||||
// The relative ordering of the prefixes within the combinations varies deterministically.
|
||||
func manyPrefixes(try func([]byte)) {
|
||||
var prefixBytes = []byte{0x66, 0x67, 0xF0, 0xF2, 0xF3, 0x3E, 0x36, 0x66, 0x67}
|
||||
var enc []byte
|
||||
for i := 0; i < 1<<uint(len(prefixBytes)); i++ {
|
||||
enc = enc[:0]
|
||||
for j, p := range prefixBytes {
|
||||
if i&(1<<uint(j)) != 0 {
|
||||
enc = append(enc, p)
|
||||
}
|
||||
}
|
||||
if len(enc) > 0 {
|
||||
k := i % len(enc)
|
||||
enc[0], enc[k] = enc[k], enc[0]
|
||||
}
|
||||
try(enc)
|
||||
}
|
||||
}
|
||||
|
||||
// basicPrefixes geneartes 8 different possible prefix cases: no prefix
|
||||
// and then one each of seven different prefix bytes.
|
||||
func basicPrefixes(try func([]byte)) {
|
||||
try(nil)
|
||||
for _, b := range []byte{0x66, 0x67, 0xF0, 0xF2, 0xF3, 0x3E, 0x36} {
|
||||
try([]byte{b})
|
||||
}
|
||||
}
|
||||
|
||||
func rexPrefixes(try func([]byte)) {
|
||||
try(nil)
|
||||
for _, b := range []byte{0x40, 0x48, 0x43, 0x4C} {
|
||||
try([]byte{b})
|
||||
}
|
||||
}
|
||||
|
||||
// concat takes two generators and returns a generator for the
|
||||
// cross product of the two, concatenating the results from each.
|
||||
func concat(gen1, gen2 func(func([]byte))) func(func([]byte)) {
|
||||
return func(try func([]byte)) {
|
||||
gen1(func(enc1 []byte) {
|
||||
gen2(func(enc2 []byte) {
|
||||
try(append(enc1[:len(enc1):len(enc1)], enc2...))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// concat3 takes three generators and returns a generator for the
|
||||
// cross product of the three, concatenating the results from each.
|
||||
func concat3(gen1, gen2, gen3 func(func([]byte))) func(func([]byte)) {
|
||||
return func(try func([]byte)) {
|
||||
gen1(func(enc1 []byte) {
|
||||
gen2(func(enc2 []byte) {
|
||||
gen3(func(enc3 []byte) {
|
||||
try(append(append(enc1[:len(enc1):len(enc1)], enc2...), enc3...))
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// concat4 takes four generators and returns a generator for the
|
||||
// cross product of the four, concatenating the results from each.
|
||||
func concat4(gen1, gen2, gen3, gen4 func(func([]byte))) func(func([]byte)) {
|
||||
return func(try func([]byte)) {
|
||||
gen1(func(enc1 []byte) {
|
||||
gen2(func(enc2 []byte) {
|
||||
gen3(func(enc3 []byte) {
|
||||
gen4(func(enc4 []byte) {
|
||||
try(append(append(append(enc1[:len(enc1):len(enc1)], enc2...), enc3...), enc4...))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// filter generates the sequences from gen that satisfy ok.
|
||||
func filter(gen func(func([]byte)), ok func([]byte) bool) func(func([]byte)) {
|
||||
return func(try func([]byte)) {
|
||||
gen(func(enc []byte) {
|
||||
if ok(enc) {
|
||||
try(enc)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// enum8bit generates all possible 1-byte sequences, followed by distinctive padding.
|
||||
func enum8bit(try func([]byte)) {
|
||||
for i := 0; i < 1<<8; i++ {
|
||||
try([]byte{byte(i), 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88})
|
||||
}
|
||||
}
|
||||
|
||||
// enum8bit generates all possible 2-byte sequences, followed by distinctive padding.
|
||||
func enum16bit(try func([]byte)) {
|
||||
for i := 0; i < 1<<16; i++ {
|
||||
try([]byte{byte(i), byte(i >> 8), 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88})
|
||||
}
|
||||
}
|
||||
|
||||
// enum24bit generates all possible 3-byte sequences, followed by distinctive padding.
|
||||
func enum24bit(try func([]byte)) {
|
||||
for i := 0; i < 1<<24; i++ {
|
||||
try([]byte{byte(i), byte(i >> 8), byte(i >> 16), 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88})
|
||||
}
|
||||
}
|
||||
|
||||
// enumModRM generates all possible modrm bytes and, for modrm values that indicate
|
||||
// a following sib byte, all possible modrm, sib combinations.
|
||||
func enumModRM(try func([]byte)) {
|
||||
for i := 0; i < 256; i++ {
|
||||
if (i>>3)&07 == 04 && i>>6 != 3 { // has sib
|
||||
for j := 0; j < 256; j++ {
|
||||
try([]byte{0, byte(i), byte(j), 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}) // byte encodings
|
||||
try([]byte{1, byte(i), byte(j), 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}) // word encodings
|
||||
}
|
||||
} else {
|
||||
try([]byte{0, byte(i), 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}) // byte encodings
|
||||
try([]byte{1, byte(i), 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}) // word encodings
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fixed generates the single case b.
|
||||
// It's mainly useful to prepare an argument for concat or concat3.
|
||||
func fixed(b ...byte) func(func([]byte)) {
|
||||
return func(try func([]byte)) {
|
||||
try(b)
|
||||
}
|
||||
}
|
||||
|
||||
// testBasic runs the given test function with cases all using opcode as the initial opcode bytes.
|
||||
// It runs three phases:
|
||||
//
|
||||
// First, zero-or-one prefixes followed by opcode followed by all possible 1-byte values.
|
||||
// If in -short mode, that's all.
|
||||
//
|
||||
// Second, zero-or-one prefixes followed by opcode followed by all possible 2-byte values.
|
||||
// If not in -long mode, that's all. This phase and the next run in parallel with other tests
|
||||
// (using t.Parallel).
|
||||
//
|
||||
// Finally, opcode followed by all possible 3-byte values. The test can take a very long time
|
||||
// and prints progress messages to package log.
|
||||
func testBasic(t *testing.T, testfn func(*testing.T, func(func([]byte))), opcode ...byte) {
|
||||
testfn(t, concat3(basicPrefixes, fixed(opcode...), enum8bit))
|
||||
if testing.Short() {
|
||||
return
|
||||
}
|
||||
|
||||
t.Parallel()
|
||||
testfn(t, concat3(basicPrefixes, fixed(opcode...), enum16bit))
|
||||
if !*longTest {
|
||||
return
|
||||
}
|
||||
|
||||
name := caller(2)
|
||||
op1 := make([]byte, len(opcode)+1)
|
||||
copy(op1, opcode)
|
||||
for i := 0; i < 256; i++ {
|
||||
log.Printf("%s 24-bit: %d/256\n", name, i)
|
||||
op1[len(opcode)] = byte(i)
|
||||
testfn(t, concat(fixed(op1...), enum16bit))
|
||||
}
|
||||
}
|
||||
|
||||
func testBasicREX(t *testing.T, testfn func(*testing.T, func(func([]byte))), opcode ...byte) {
|
||||
testfn(t, filter(concat4(basicPrefixes, rexPrefixes, fixed(opcode...), enum8bit), isValidREX))
|
||||
if testing.Short() {
|
||||
return
|
||||
}
|
||||
|
||||
t.Parallel()
|
||||
testfn(t, filter(concat4(basicPrefixes, rexPrefixes, fixed(opcode...), enum16bit), isValidREX))
|
||||
if !*longTest {
|
||||
return
|
||||
}
|
||||
|
||||
name := caller(2)
|
||||
op1 := make([]byte, len(opcode)+1)
|
||||
copy(op1, opcode)
|
||||
for i := 0; i < 256; i++ {
|
||||
log.Printf("%s 24-bit: %d/256\n", name, i)
|
||||
op1[len(opcode)] = byte(i)
|
||||
testfn(t, filter(concat3(rexPrefixes, fixed(op1...), enum16bit), isValidREX))
|
||||
}
|
||||
}
|
||||
|
||||
// testPrefix runs the given test function for all many prefix possibilities
|
||||
// followed by all possible 1-byte sequences.
|
||||
//
|
||||
// If in -long mode, it then runs a test of all the prefix possibilities followed
|
||||
// by all possible 2-byte sequences.
|
||||
func testPrefix(t *testing.T, testfn func(*testing.T, func(func([]byte)))) {
|
||||
t.Parallel()
|
||||
testfn(t, concat(manyPrefixes, enum8bit))
|
||||
if testing.Short() || !*longTest {
|
||||
return
|
||||
}
|
||||
|
||||
name := caller(2)
|
||||
for i := 0; i < 256; i++ {
|
||||
log.Printf("%s 16-bit: %d/256\n", name, i)
|
||||
testfn(t, concat3(manyPrefixes, fixed(byte(i)), enum8bit))
|
||||
}
|
||||
}
|
||||
|
||||
func testPrefixREX(t *testing.T, testfn func(*testing.T, func(func([]byte)))) {
|
||||
t.Parallel()
|
||||
testfn(t, filter(concat3(manyPrefixes, rexPrefixes, enum8bit), isValidREX))
|
||||
if testing.Short() || !*longTest {
|
||||
return
|
||||
}
|
||||
|
||||
name := caller(2)
|
||||
for i := 0; i < 256; i++ {
|
||||
log.Printf("%s 16-bit: %d/256\n", name, i)
|
||||
testfn(t, filter(concat4(manyPrefixes, rexPrefixes, fixed(byte(i)), enum8bit), isValidREX))
|
||||
}
|
||||
}
|
||||
|
||||
func caller(skip int) string {
|
||||
pc, _, _, _ := runtime.Caller(skip)
|
||||
f := runtime.FuncForPC(pc)
|
||||
name := "?"
|
||||
if f != nil {
|
||||
name = f.Name()
|
||||
if i := strings.LastIndex(name, "."); i >= 0 {
|
||||
name = name[i+1:]
|
||||
}
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func isValidREX(x []byte) bool {
|
||||
i := 0
|
||||
for i < len(x) && isPrefixByte(x[i]) {
|
||||
i++
|
||||
}
|
||||
if i < len(x) && Prefix(x[i]).IsREX() {
|
||||
i++
|
||||
if i < len(x) {
|
||||
return !isPrefixByte(x[i]) && !Prefix(x[i]).IsREX()
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func isPrefixByte(b byte) bool {
|
||||
switch b {
|
||||
case 0x26, 0x2E, 0x36, 0x3E, 0x64, 0x65, 0x66, 0x67, 0xF0, 0xF2, 0xF3:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package x86asm
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testFormattingSymname(addr uint64) (string, uint64) {
|
||||
switch addr {
|
||||
case 0x424080:
|
||||
return "runtime.printint", 0x424080
|
||||
case 0x4c8068:
|
||||
return "main.A", 0x4c8068
|
||||
}
|
||||
return "", 0
|
||||
}
|
||||
|
||||
func TestFormatting(t *testing.T) {
|
||||
testCases := []struct {
|
||||
PC uint64
|
||||
bytes string
|
||||
|
||||
goSyntax, intelSyntax, gnuSyntax string
|
||||
}{
|
||||
{0x4816b2, "0f8677010000",
|
||||
"JBE 0x48182f",
|
||||
"jbe 0x48182f",
|
||||
"jbe 0x48182f"},
|
||||
{0x45065b, "488b442408",
|
||||
"MOVQ 0x8(SP), AX",
|
||||
"mov rax, qword ptr [rsp+0x8]",
|
||||
"mov 0x8(%rsp),%rax"},
|
||||
{0x450678, "488b05e9790700",
|
||||
"MOVQ main.A(SB), AX",
|
||||
"mov rax, qword ptr [main.A]",
|
||||
"mov main.A,%rax"},
|
||||
{0x450664, "e8173afdff",
|
||||
"CALL runtime.printint(SB)",
|
||||
"call runtime.printint",
|
||||
"callq runtime.printint"},
|
||||
{0x45069b, "488d0575d90100",
|
||||
"LEAQ 0x1d975(IP), AX",
|
||||
"lea rax, ptr [rip+0x1d975]",
|
||||
"lea 0x1d975(%rip),%rax"},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Logf("%#x %s %s", testCase.PC, testCase.bytes, testCase.goSyntax)
|
||||
bs, _ := hex.DecodeString(testCase.bytes)
|
||||
inst, err := Decode(bs, 64)
|
||||
if err != nil {
|
||||
t.Errorf("decode error %v", err)
|
||||
}
|
||||
if out := GoSyntax(inst, testCase.PC, testFormattingSymname); out != testCase.goSyntax {
|
||||
t.Errorf("GoSyntax: %q", out)
|
||||
}
|
||||
if out := IntelSyntax(inst, testCase.PC, testFormattingSymname); out != testCase.intelSyntax {
|
||||
t.Errorf("IntelSyntax: %q expected: %q", out, testCase.intelSyntax)
|
||||
}
|
||||
if out := GNUSyntax(inst, testCase.PC, testFormattingSymname); out != testCase.gnuSyntax {
|
||||
t.Errorf("GNUSyntax: %q expected: %q", out, testCase.gnuSyntax)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,956 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package x86asm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GNUSyntax returns the GNU assembler syntax for the instruction, as defined by GNU binutils.
|
||||
// This general form is often called ``AT&T syntax'' as a reference to AT&T System V Unix.
|
||||
func GNUSyntax(inst Inst, pc uint64, symname SymLookup) string {
|
||||
// Rewrite instruction to mimic GNU peculiarities.
|
||||
// Note that inst has been passed by value and contains
|
||||
// no pointers, so any changes we make here are local
|
||||
// and will not propagate back out to the caller.
|
||||
|
||||
if symname == nil {
|
||||
symname = func(uint64) (string, uint64) { return "", 0 }
|
||||
}
|
||||
|
||||
// Adjust opcode [sic].
|
||||
switch inst.Op {
|
||||
case FDIV, FDIVR, FSUB, FSUBR, FDIVP, FDIVRP, FSUBP, FSUBRP:
|
||||
// DC E0, DC F0: libopcodes swaps FSUBR/FSUB and FDIVR/FDIV, at least
|
||||
// if you believe the Intel manual is correct (the encoding is irregular as given;
|
||||
// libopcodes uses the more regular expected encoding).
|
||||
// TODO(rsc): Test to ensure Intel manuals are correct and report to libopcodes maintainers?
|
||||
// NOTE: iant thinks this is deliberate, but we can't find the history.
|
||||
_, reg1 := inst.Args[0].(Reg)
|
||||
_, reg2 := inst.Args[1].(Reg)
|
||||
if reg1 && reg2 && (inst.Opcode>>24 == 0xDC || inst.Opcode>>24 == 0xDE) {
|
||||
switch inst.Op {
|
||||
case FDIV:
|
||||
inst.Op = FDIVR
|
||||
case FDIVR:
|
||||
inst.Op = FDIV
|
||||
case FSUB:
|
||||
inst.Op = FSUBR
|
||||
case FSUBR:
|
||||
inst.Op = FSUB
|
||||
case FDIVP:
|
||||
inst.Op = FDIVRP
|
||||
case FDIVRP:
|
||||
inst.Op = FDIVP
|
||||
case FSUBP:
|
||||
inst.Op = FSUBRP
|
||||
case FSUBRP:
|
||||
inst.Op = FSUBP
|
||||
}
|
||||
}
|
||||
|
||||
case MOVNTSD:
|
||||
// MOVNTSD is F2 0F 2B /r.
|
||||
// MOVNTSS is F3 0F 2B /r (supposedly; not in manuals).
|
||||
// Usually inner prefixes win for display,
|
||||
// so that F3 F2 0F 2B 11 is REP MOVNTSD
|
||||
// and F2 F3 0F 2B 11 is REPN MOVNTSS.
|
||||
// Libopcodes always prefers MOVNTSS regardless of prefix order.
|
||||
if countPrefix(&inst, 0xF3) > 0 {
|
||||
found := false
|
||||
for i := len(inst.Prefix) - 1; i >= 0; i-- {
|
||||
switch inst.Prefix[i] & 0xFF {
|
||||
case 0xF3:
|
||||
if !found {
|
||||
found = true
|
||||
inst.Prefix[i] |= PrefixImplicit
|
||||
}
|
||||
case 0xF2:
|
||||
inst.Prefix[i] &^= PrefixImplicit
|
||||
}
|
||||
}
|
||||
inst.Op = MOVNTSS
|
||||
}
|
||||
}
|
||||
|
||||
// Add implicit arguments.
|
||||
switch inst.Op {
|
||||
case MONITOR:
|
||||
inst.Args[0] = EDX
|
||||
inst.Args[1] = ECX
|
||||
inst.Args[2] = EAX
|
||||
if inst.AddrSize == 16 {
|
||||
inst.Args[2] = AX
|
||||
}
|
||||
|
||||
case MWAIT:
|
||||
if inst.Mode == 64 {
|
||||
inst.Args[0] = RCX
|
||||
inst.Args[1] = RAX
|
||||
} else {
|
||||
inst.Args[0] = ECX
|
||||
inst.Args[1] = EAX
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust which prefixes will be displayed.
|
||||
// The rule is to display all the prefixes not implied by
|
||||
// the usual instruction display, that is, all the prefixes
|
||||
// except the ones with PrefixImplicit set.
|
||||
// However, of course, there are exceptions to the rule.
|
||||
switch inst.Op {
|
||||
case CRC32:
|
||||
// CRC32 has a mandatory F2 prefix.
|
||||
// If there are multiple F2s and no F3s, the extra F2s do not print.
|
||||
// (And Decode has already marked them implicit.)
|
||||
// However, if there is an F3 anywhere, then the extra F2s do print.
|
||||
// If there are multiple F2 prefixes *and* an (ignored) F3,
|
||||
// then libopcodes prints the extra F2s as REPNs.
|
||||
if countPrefix(&inst, 0xF2) > 1 {
|
||||
unmarkImplicit(&inst, 0xF2)
|
||||
markLastImplicit(&inst, 0xF2)
|
||||
}
|
||||
|
||||
// An unused data size override should probably be shown,
|
||||
// to distinguish DATA16 CRC32B from plain CRC32B,
|
||||
// but libopcodes always treats the final override as implicit
|
||||
// and the others as explicit.
|
||||
unmarkImplicit(&inst, PrefixDataSize)
|
||||
markLastImplicit(&inst, PrefixDataSize)
|
||||
|
||||
case CVTSI2SD, CVTSI2SS:
|
||||
if !isMem(inst.Args[1]) {
|
||||
markLastImplicit(&inst, PrefixDataSize)
|
||||
}
|
||||
|
||||
case CVTSD2SI, CVTSS2SI, CVTTSD2SI, CVTTSS2SI,
|
||||
ENTER, FLDENV, FNSAVE, FNSTENV, FRSTOR, LGDT, LIDT, LRET,
|
||||
POP, PUSH, RET, SGDT, SIDT, SYSRET, XBEGIN:
|
||||
markLastImplicit(&inst, PrefixDataSize)
|
||||
|
||||
case LOOP, LOOPE, LOOPNE, MONITOR:
|
||||
markLastImplicit(&inst, PrefixAddrSize)
|
||||
|
||||
case MOV:
|
||||
// The 16-bit and 32-bit forms of MOV Sreg, dst and MOV src, Sreg
|
||||
// cannot be distinguished when src or dst refers to memory, because
|
||||
// Sreg is always a 16-bit value, even when we're doing a 32-bit
|
||||
// instruction. Because the instruction tables distinguished these two,
|
||||
// any operand size prefix has been marked as used (to decide which
|
||||
// branch to take). Unmark it, so that it will show up in disassembly,
|
||||
// so that the reader can tell the size of memory operand.
|
||||
// up with the same arguments
|
||||
dst, _ := inst.Args[0].(Reg)
|
||||
src, _ := inst.Args[1].(Reg)
|
||||
if ES <= src && src <= GS && isMem(inst.Args[0]) || ES <= dst && dst <= GS && isMem(inst.Args[1]) {
|
||||
unmarkImplicit(&inst, PrefixDataSize)
|
||||
}
|
||||
|
||||
case MOVDQU:
|
||||
if countPrefix(&inst, 0xF3) > 1 {
|
||||
unmarkImplicit(&inst, 0xF3)
|
||||
markLastImplicit(&inst, 0xF3)
|
||||
}
|
||||
|
||||
case MOVQ2DQ:
|
||||
markLastImplicit(&inst, PrefixDataSize)
|
||||
|
||||
case SLDT, SMSW, STR, FXRSTOR, XRSTOR, XSAVE, XSAVEOPT, CMPXCHG8B:
|
||||
if isMem(inst.Args[0]) {
|
||||
unmarkImplicit(&inst, PrefixDataSize)
|
||||
}
|
||||
|
||||
case SYSEXIT:
|
||||
unmarkImplicit(&inst, PrefixDataSize)
|
||||
}
|
||||
|
||||
if isCondJmp[inst.Op] || isLoop[inst.Op] || inst.Op == JCXZ || inst.Op == JECXZ || inst.Op == JRCXZ {
|
||||
if countPrefix(&inst, PrefixCS) > 0 && countPrefix(&inst, PrefixDS) > 0 {
|
||||
for i, p := range inst.Prefix {
|
||||
switch p & 0xFFF {
|
||||
case PrefixPN, PrefixPT:
|
||||
inst.Prefix[i] &= 0xF0FF // cut interpretation bits, producing original segment prefix
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// XACQUIRE/XRELEASE adjustment.
|
||||
if inst.Op == MOV {
|
||||
// MOV into memory is a candidate for turning REP into XRELEASE.
|
||||
// However, if the REP is followed by a REPN, that REPN blocks the
|
||||
// conversion.
|
||||
haveREPN := false
|
||||
for i := len(inst.Prefix) - 1; i >= 0; i-- {
|
||||
switch inst.Prefix[i] &^ PrefixIgnored {
|
||||
case PrefixREPN:
|
||||
haveREPN = true
|
||||
case PrefixXRELEASE:
|
||||
if haveREPN {
|
||||
inst.Prefix[i] = PrefixREP
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We only format the final F2/F3 as XRELEASE/XACQUIRE.
|
||||
haveXA := false
|
||||
haveXR := false
|
||||
for i := len(inst.Prefix) - 1; i >= 0; i-- {
|
||||
switch inst.Prefix[i] &^ PrefixIgnored {
|
||||
case PrefixXRELEASE:
|
||||
if !haveXR {
|
||||
haveXR = true
|
||||
} else {
|
||||
inst.Prefix[i] = PrefixREP
|
||||
}
|
||||
|
||||
case PrefixXACQUIRE:
|
||||
if !haveXA {
|
||||
haveXA = true
|
||||
} else {
|
||||
inst.Prefix[i] = PrefixREPN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Determine opcode.
|
||||
op := strings.ToLower(inst.Op.String())
|
||||
if alt := gnuOp[inst.Op]; alt != "" {
|
||||
op = alt
|
||||
}
|
||||
|
||||
// Determine opcode suffix.
|
||||
// Libopcodes omits the suffix if the width of the operation
|
||||
// can be inferred from a register arguments. For example,
|
||||
// add $1, %ebx has no suffix because you can tell from the
|
||||
// 32-bit register destination that it is a 32-bit add,
|
||||
// but in addl $1, (%ebx), the destination is memory, so the
|
||||
// size is not evident without the l suffix.
|
||||
needSuffix := true
|
||||
SuffixLoop:
|
||||
for i, a := range inst.Args {
|
||||
if a == nil {
|
||||
break
|
||||
}
|
||||
switch a := a.(type) {
|
||||
case Reg:
|
||||
switch inst.Op {
|
||||
case MOVSX, MOVZX:
|
||||
continue
|
||||
|
||||
case SHL, SHR, RCL, RCR, ROL, ROR, SAR:
|
||||
if i == 1 {
|
||||
// shift count does not tell us operand size
|
||||
continue
|
||||
}
|
||||
|
||||
case CRC32:
|
||||
// The source argument does tell us operand size,
|
||||
// but libopcodes still always puts a suffix on crc32.
|
||||
continue
|
||||
|
||||
case PUSH, POP:
|
||||
// Even though segment registers are 16-bit, push and pop
|
||||
// can save/restore them from 32-bit slots, so they
|
||||
// do not imply operand size.
|
||||
if ES <= a && a <= GS {
|
||||
continue
|
||||
}
|
||||
|
||||
case CVTSI2SD, CVTSI2SS:
|
||||
// The integer register argument takes priority.
|
||||
if X0 <= a && a <= X15 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if AL <= a && a <= R15 || ES <= a && a <= GS || X0 <= a && a <= X15 || M0 <= a && a <= M7 {
|
||||
needSuffix = false
|
||||
break SuffixLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if needSuffix {
|
||||
switch inst.Op {
|
||||
case CMPXCHG8B, FLDCW, FNSTCW, FNSTSW, LDMXCSR, LLDT, LMSW, LTR, PCLMULQDQ,
|
||||
SETA, SETAE, SETB, SETBE, SETE, SETG, SETGE, SETL, SETLE, SETNE, SETNO, SETNP, SETNS, SETO, SETP, SETS,
|
||||
SLDT, SMSW, STMXCSR, STR, VERR, VERW:
|
||||
// For various reasons, libopcodes emits no suffix for these instructions.
|
||||
|
||||
case CRC32:
|
||||
op += byteSizeSuffix(argBytes(&inst, inst.Args[1]))
|
||||
|
||||
case LGDT, LIDT, SGDT, SIDT:
|
||||
op += byteSizeSuffix(inst.DataSize / 8)
|
||||
|
||||
case MOVZX, MOVSX:
|
||||
// Integer size conversions get two suffixes.
|
||||
op = op[:4] + byteSizeSuffix(argBytes(&inst, inst.Args[1])) + byteSizeSuffix(argBytes(&inst, inst.Args[0]))
|
||||
|
||||
case LOOP, LOOPE, LOOPNE:
|
||||
// Add w suffix to indicate use of CX register instead of ECX.
|
||||
if inst.AddrSize == 16 {
|
||||
op += "w"
|
||||
}
|
||||
|
||||
case CALL, ENTER, JMP, LCALL, LEAVE, LJMP, LRET, RET, SYSRET, XBEGIN:
|
||||
// Add w suffix to indicate use of 16-bit target.
|
||||
// Exclude JMP rel8.
|
||||
if inst.Opcode>>24 == 0xEB {
|
||||
break
|
||||
}
|
||||
if inst.DataSize == 16 && inst.Mode != 16 {
|
||||
markLastImplicit(&inst, PrefixDataSize)
|
||||
op += "w"
|
||||
} else if inst.Mode == 64 {
|
||||
op += "q"
|
||||
}
|
||||
|
||||
case FRSTOR, FNSAVE, FNSTENV, FLDENV:
|
||||
// Add s suffix to indicate shortened FPU state (I guess).
|
||||
if inst.DataSize == 16 {
|
||||
op += "s"
|
||||
}
|
||||
|
||||
case PUSH, POP:
|
||||
if markLastImplicit(&inst, PrefixDataSize) {
|
||||
op += byteSizeSuffix(inst.DataSize / 8)
|
||||
} else if inst.Mode == 64 {
|
||||
op += "q"
|
||||
} else {
|
||||
op += byteSizeSuffix(inst.MemBytes)
|
||||
}
|
||||
|
||||
default:
|
||||
if isFloat(inst.Op) {
|
||||
// I can't explain any of this, but it's what libopcodes does.
|
||||
switch inst.MemBytes {
|
||||
default:
|
||||
if (inst.Op == FLD || inst.Op == FSTP) && isMem(inst.Args[0]) {
|
||||
op += "t"
|
||||
}
|
||||
case 4:
|
||||
if isFloatInt(inst.Op) {
|
||||
op += "l"
|
||||
} else {
|
||||
op += "s"
|
||||
}
|
||||
case 8:
|
||||
if isFloatInt(inst.Op) {
|
||||
op += "ll"
|
||||
} else {
|
||||
op += "l"
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
op += byteSizeSuffix(inst.MemBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust special case opcodes.
|
||||
switch inst.Op {
|
||||
case 0:
|
||||
if inst.Prefix[0] != 0 {
|
||||
return strings.ToLower(inst.Prefix[0].String())
|
||||
}
|
||||
|
||||
case INT:
|
||||
if inst.Opcode>>24 == 0xCC {
|
||||
inst.Args[0] = nil
|
||||
op = "int3"
|
||||
}
|
||||
|
||||
case CMPPS, CMPPD, CMPSD_XMM, CMPSS:
|
||||
imm, ok := inst.Args[2].(Imm)
|
||||
if ok && 0 <= imm && imm < 8 {
|
||||
inst.Args[2] = nil
|
||||
op = cmppsOps[imm] + op[3:]
|
||||
}
|
||||
|
||||
case PCLMULQDQ:
|
||||
imm, ok := inst.Args[2].(Imm)
|
||||
if ok && imm&^0x11 == 0 {
|
||||
inst.Args[2] = nil
|
||||
op = pclmulqOps[(imm&0x10)>>3|(imm&1)]
|
||||
}
|
||||
|
||||
case XLATB:
|
||||
if markLastImplicit(&inst, PrefixAddrSize) {
|
||||
op = "xlat" // not xlatb
|
||||
}
|
||||
}
|
||||
|
||||
// Build list of argument strings.
|
||||
var (
|
||||
usedPrefixes bool // segment prefixes consumed by Mem formatting
|
||||
args []string // formatted arguments
|
||||
)
|
||||
for i, a := range inst.Args {
|
||||
if a == nil {
|
||||
break
|
||||
}
|
||||
switch inst.Op {
|
||||
case MOVSB, MOVSW, MOVSD, MOVSQ, OUTSB, OUTSW, OUTSD:
|
||||
if i == 0 {
|
||||
usedPrefixes = true // disable use of prefixes for first argument
|
||||
} else {
|
||||
usedPrefixes = false
|
||||
}
|
||||
}
|
||||
if a == Imm(1) && (inst.Opcode>>24)&^1 == 0xD0 {
|
||||
continue
|
||||
}
|
||||
args = append(args, gnuArg(&inst, pc, symname, a, &usedPrefixes))
|
||||
}
|
||||
|
||||
// The default is to print the arguments in reverse Intel order.
|
||||
// A few instructions inhibit this behavior.
|
||||
switch inst.Op {
|
||||
case BOUND, LCALL, ENTER, LJMP:
|
||||
// no reverse
|
||||
default:
|
||||
// reverse args
|
||||
for i, j := 0, len(args)-1; i < j; i, j = i+1, j-1 {
|
||||
args[i], args[j] = args[j], args[i]
|
||||
}
|
||||
}
|
||||
|
||||
// Build prefix string.
|
||||
// Must be after argument formatting, which can turn off segment prefixes.
|
||||
var (
|
||||
prefix = "" // output string
|
||||
numAddr = 0
|
||||
numData = 0
|
||||
implicitData = false
|
||||
)
|
||||
for _, p := range inst.Prefix {
|
||||
if p&0xFF == PrefixDataSize && p&PrefixImplicit != 0 {
|
||||
implicitData = true
|
||||
}
|
||||
}
|
||||
for _, p := range inst.Prefix {
|
||||
if p == 0 || p.IsVEX() {
|
||||
break
|
||||
}
|
||||
if p&PrefixImplicit != 0 {
|
||||
continue
|
||||
}
|
||||
switch p &^ (PrefixIgnored | PrefixInvalid) {
|
||||
default:
|
||||
if p.IsREX() {
|
||||
if p&0xFF == PrefixREX {
|
||||
prefix += "rex "
|
||||
} else {
|
||||
prefix += "rex." + p.String()[4:] + " "
|
||||
}
|
||||
break
|
||||
}
|
||||
prefix += strings.ToLower(p.String()) + " "
|
||||
|
||||
case PrefixPN:
|
||||
op += ",pn"
|
||||
continue
|
||||
|
||||
case PrefixPT:
|
||||
op += ",pt"
|
||||
continue
|
||||
|
||||
case PrefixAddrSize, PrefixAddr16, PrefixAddr32:
|
||||
// For unknown reasons, if the addr16 prefix is repeated,
|
||||
// libopcodes displays all but the last as addr32, even though
|
||||
// the addressing form used in a memory reference is clearly
|
||||
// still 16-bit.
|
||||
n := 32
|
||||
if inst.Mode == 32 {
|
||||
n = 16
|
||||
}
|
||||
numAddr++
|
||||
if countPrefix(&inst, PrefixAddrSize) > numAddr {
|
||||
n = inst.Mode
|
||||
}
|
||||
prefix += fmt.Sprintf("addr%d ", n)
|
||||
continue
|
||||
|
||||
case PrefixData16, PrefixData32:
|
||||
if implicitData && countPrefix(&inst, PrefixDataSize) > 1 {
|
||||
// Similar to the addr32 logic above, but it only kicks in
|
||||
// when something used the data size prefix (one is implicit).
|
||||
n := 16
|
||||
if inst.Mode == 16 {
|
||||
n = 32
|
||||
}
|
||||
numData++
|
||||
if countPrefix(&inst, PrefixDataSize) > numData {
|
||||
if inst.Mode == 16 {
|
||||
n = 16
|
||||
} else {
|
||||
n = 32
|
||||
}
|
||||
}
|
||||
prefix += fmt.Sprintf("data%d ", n)
|
||||
continue
|
||||
}
|
||||
prefix += strings.ToLower(p.String()) + " "
|
||||
}
|
||||
}
|
||||
|
||||
// Finally! Put it all together.
|
||||
text := prefix + op
|
||||
if args != nil {
|
||||
text += " "
|
||||
// Indirect call/jmp gets a star to distinguish from direct jump address.
|
||||
if (inst.Op == CALL || inst.Op == JMP || inst.Op == LJMP || inst.Op == LCALL) && (isMem(inst.Args[0]) || isReg(inst.Args[0])) {
|
||||
text += "*"
|
||||
}
|
||||
text += strings.Join(args, ",")
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
// gnuArg returns the GNU syntax for the argument x from the instruction inst.
|
||||
// If *usedPrefixes is false and x is a Mem, then the formatting
|
||||
// includes any segment prefixes and sets *usedPrefixes to true.
|
||||
func gnuArg(inst *Inst, pc uint64, symname SymLookup, x Arg, usedPrefixes *bool) string {
|
||||
if x == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
switch x := x.(type) {
|
||||
case Reg:
|
||||
switch inst.Op {
|
||||
case CVTSI2SS, CVTSI2SD, CVTSS2SI, CVTSD2SI, CVTTSD2SI, CVTTSS2SI:
|
||||
if inst.DataSize == 16 && EAX <= x && x <= R15L {
|
||||
x -= EAX - AX
|
||||
}
|
||||
|
||||
case IN, INSB, INSW, INSD, OUT, OUTSB, OUTSW, OUTSD:
|
||||
// DX is the port, but libopcodes prints it as if it were a memory reference.
|
||||
if x == DX {
|
||||
return "(%dx)"
|
||||
}
|
||||
case VMOVDQA, VMOVDQU, VMOVNTDQA, VMOVNTDQ:
|
||||
return strings.Replace(gccRegName[x], "xmm", "ymm", -1)
|
||||
}
|
||||
return gccRegName[x]
|
||||
case Mem:
|
||||
if s, disp := memArgToSymbol(x, pc, inst.Len, symname); s != "" {
|
||||
suffix := ""
|
||||
if disp != 0 {
|
||||
suffix = fmt.Sprintf("%+d", disp)
|
||||
}
|
||||
return fmt.Sprintf("%s%s", s, suffix)
|
||||
}
|
||||
seg := ""
|
||||
var haveCS, haveDS, haveES, haveFS, haveGS, haveSS bool
|
||||
switch x.Segment {
|
||||
case CS:
|
||||
haveCS = true
|
||||
case DS:
|
||||
haveDS = true
|
||||
case ES:
|
||||
haveES = true
|
||||
case FS:
|
||||
haveFS = true
|
||||
case GS:
|
||||
haveGS = true
|
||||
case SS:
|
||||
haveSS = true
|
||||
}
|
||||
switch inst.Op {
|
||||
case INSB, INSW, INSD, STOSB, STOSW, STOSD, STOSQ, SCASB, SCASW, SCASD, SCASQ:
|
||||
// These do not accept segment prefixes, at least in the GNU rendering.
|
||||
default:
|
||||
if *usedPrefixes {
|
||||
break
|
||||
}
|
||||
for i := len(inst.Prefix) - 1; i >= 0; i-- {
|
||||
p := inst.Prefix[i] &^ PrefixIgnored
|
||||
if p == 0 {
|
||||
continue
|
||||
}
|
||||
switch p {
|
||||
case PrefixCS:
|
||||
if !haveCS {
|
||||
haveCS = true
|
||||
inst.Prefix[i] |= PrefixImplicit
|
||||
}
|
||||
case PrefixDS:
|
||||
if !haveDS {
|
||||
haveDS = true
|
||||
inst.Prefix[i] |= PrefixImplicit
|
||||
}
|
||||
case PrefixES:
|
||||
if !haveES {
|
||||
haveES = true
|
||||
inst.Prefix[i] |= PrefixImplicit
|
||||
}
|
||||
case PrefixFS:
|
||||
if !haveFS {
|
||||
haveFS = true
|
||||
inst.Prefix[i] |= PrefixImplicit
|
||||
}
|
||||
case PrefixGS:
|
||||
if !haveGS {
|
||||
haveGS = true
|
||||
inst.Prefix[i] |= PrefixImplicit
|
||||
}
|
||||
case PrefixSS:
|
||||
if !haveSS {
|
||||
haveSS = true
|
||||
inst.Prefix[i] |= PrefixImplicit
|
||||
}
|
||||
}
|
||||
}
|
||||
*usedPrefixes = true
|
||||
}
|
||||
if haveCS {
|
||||
seg += "%cs:"
|
||||
}
|
||||
if haveDS {
|
||||
seg += "%ds:"
|
||||
}
|
||||
if haveSS {
|
||||
seg += "%ss:"
|
||||
}
|
||||
if haveES {
|
||||
seg += "%es:"
|
||||
}
|
||||
if haveFS {
|
||||
seg += "%fs:"
|
||||
}
|
||||
if haveGS {
|
||||
seg += "%gs:"
|
||||
}
|
||||
disp := ""
|
||||
if x.Disp != 0 {
|
||||
disp = fmt.Sprintf("%#x", x.Disp)
|
||||
}
|
||||
if x.Scale == 0 || x.Index == 0 && x.Scale == 1 && (x.Base == ESP || x.Base == RSP || x.Base == 0 && inst.Mode == 64) {
|
||||
if x.Base == 0 {
|
||||
return seg + disp
|
||||
}
|
||||
return fmt.Sprintf("%s%s(%s)", seg, disp, gccRegName[x.Base])
|
||||
}
|
||||
base := gccRegName[x.Base]
|
||||
if x.Base == 0 {
|
||||
base = ""
|
||||
}
|
||||
index := gccRegName[x.Index]
|
||||
if x.Index == 0 {
|
||||
if inst.AddrSize == 64 {
|
||||
index = "%riz"
|
||||
} else {
|
||||
index = "%eiz"
|
||||
}
|
||||
}
|
||||
if AX <= x.Base && x.Base <= DI {
|
||||
// 16-bit addressing - no scale
|
||||
return fmt.Sprintf("%s%s(%s,%s)", seg, disp, base, index)
|
||||
}
|
||||
return fmt.Sprintf("%s%s(%s,%s,%d)", seg, disp, base, index, x.Scale)
|
||||
case Rel:
|
||||
if pc == 0 {
|
||||
return fmt.Sprintf(".%+#x", int64(x))
|
||||
} else {
|
||||
addr := pc + uint64(inst.Len) + uint64(x)
|
||||
if s, base := symname(addr); s != "" && addr == base {
|
||||
return fmt.Sprintf("%s", s)
|
||||
} else {
|
||||
addr := pc + uint64(inst.Len) + uint64(x)
|
||||
return fmt.Sprintf("%#x", addr)
|
||||
}
|
||||
}
|
||||
case Imm:
|
||||
if s, base := symname(uint64(x)); s != "" {
|
||||
suffix := ""
|
||||
if uint64(x) != base {
|
||||
suffix = fmt.Sprintf("%+d", uint64(x)-base)
|
||||
}
|
||||
return fmt.Sprintf("$%s%s", s, suffix)
|
||||
}
|
||||
if inst.Mode == 32 {
|
||||
return fmt.Sprintf("$%#x", uint32(x))
|
||||
}
|
||||
return fmt.Sprintf("$%#x", int64(x))
|
||||
}
|
||||
return x.String()
|
||||
}
|
||||
|
||||
var gccRegName = [...]string{
|
||||
0: "REG0",
|
||||
AL: "%al",
|
||||
CL: "%cl",
|
||||
BL: "%bl",
|
||||
DL: "%dl",
|
||||
AH: "%ah",
|
||||
CH: "%ch",
|
||||
BH: "%bh",
|
||||
DH: "%dh",
|
||||
SPB: "%spl",
|
||||
BPB: "%bpl",
|
||||
SIB: "%sil",
|
||||
DIB: "%dil",
|
||||
R8B: "%r8b",
|
||||
R9B: "%r9b",
|
||||
R10B: "%r10b",
|
||||
R11B: "%r11b",
|
||||
R12B: "%r12b",
|
||||
R13B: "%r13b",
|
||||
R14B: "%r14b",
|
||||
R15B: "%r15b",
|
||||
AX: "%ax",
|
||||
CX: "%cx",
|
||||
BX: "%bx",
|
||||
DX: "%dx",
|
||||
SP: "%sp",
|
||||
BP: "%bp",
|
||||
SI: "%si",
|
||||
DI: "%di",
|
||||
R8W: "%r8w",
|
||||
R9W: "%r9w",
|
||||
R10W: "%r10w",
|
||||
R11W: "%r11w",
|
||||
R12W: "%r12w",
|
||||
R13W: "%r13w",
|
||||
R14W: "%r14w",
|
||||
R15W: "%r15w",
|
||||
EAX: "%eax",
|
||||
ECX: "%ecx",
|
||||
EDX: "%edx",
|
||||
EBX: "%ebx",
|
||||
ESP: "%esp",
|
||||
EBP: "%ebp",
|
||||
ESI: "%esi",
|
||||
EDI: "%edi",
|
||||
R8L: "%r8d",
|
||||
R9L: "%r9d",
|
||||
R10L: "%r10d",
|
||||
R11L: "%r11d",
|
||||
R12L: "%r12d",
|
||||
R13L: "%r13d",
|
||||
R14L: "%r14d",
|
||||
R15L: "%r15d",
|
||||
RAX: "%rax",
|
||||
RCX: "%rcx",
|
||||
RDX: "%rdx",
|
||||
RBX: "%rbx",
|
||||
RSP: "%rsp",
|
||||
RBP: "%rbp",
|
||||
RSI: "%rsi",
|
||||
RDI: "%rdi",
|
||||
R8: "%r8",
|
||||
R9: "%r9",
|
||||
R10: "%r10",
|
||||
R11: "%r11",
|
||||
R12: "%r12",
|
||||
R13: "%r13",
|
||||
R14: "%r14",
|
||||
R15: "%r15",
|
||||
IP: "%ip",
|
||||
EIP: "%eip",
|
||||
RIP: "%rip",
|
||||
F0: "%st",
|
||||
F1: "%st(1)",
|
||||
F2: "%st(2)",
|
||||
F3: "%st(3)",
|
||||
F4: "%st(4)",
|
||||
F5: "%st(5)",
|
||||
F6: "%st(6)",
|
||||
F7: "%st(7)",
|
||||
M0: "%mm0",
|
||||
M1: "%mm1",
|
||||
M2: "%mm2",
|
||||
M3: "%mm3",
|
||||
M4: "%mm4",
|
||||
M5: "%mm5",
|
||||
M6: "%mm6",
|
||||
M7: "%mm7",
|
||||
X0: "%xmm0",
|
||||
X1: "%xmm1",
|
||||
X2: "%xmm2",
|
||||
X3: "%xmm3",
|
||||
X4: "%xmm4",
|
||||
X5: "%xmm5",
|
||||
X6: "%xmm6",
|
||||
X7: "%xmm7",
|
||||
X8: "%xmm8",
|
||||
X9: "%xmm9",
|
||||
X10: "%xmm10",
|
||||
X11: "%xmm11",
|
||||
X12: "%xmm12",
|
||||
X13: "%xmm13",
|
||||
X14: "%xmm14",
|
||||
X15: "%xmm15",
|
||||
CS: "%cs",
|
||||
SS: "%ss",
|
||||
DS: "%ds",
|
||||
ES: "%es",
|
||||
FS: "%fs",
|
||||
GS: "%gs",
|
||||
GDTR: "%gdtr",
|
||||
IDTR: "%idtr",
|
||||
LDTR: "%ldtr",
|
||||
MSW: "%msw",
|
||||
TASK: "%task",
|
||||
CR0: "%cr0",
|
||||
CR1: "%cr1",
|
||||
CR2: "%cr2",
|
||||
CR3: "%cr3",
|
||||
CR4: "%cr4",
|
||||
CR5: "%cr5",
|
||||
CR6: "%cr6",
|
||||
CR7: "%cr7",
|
||||
CR8: "%cr8",
|
||||
CR9: "%cr9",
|
||||
CR10: "%cr10",
|
||||
CR11: "%cr11",
|
||||
CR12: "%cr12",
|
||||
CR13: "%cr13",
|
||||
CR14: "%cr14",
|
||||
CR15: "%cr15",
|
||||
DR0: "%db0",
|
||||
DR1: "%db1",
|
||||
DR2: "%db2",
|
||||
DR3: "%db3",
|
||||
DR4: "%db4",
|
||||
DR5: "%db5",
|
||||
DR6: "%db6",
|
||||
DR7: "%db7",
|
||||
TR0: "%tr0",
|
||||
TR1: "%tr1",
|
||||
TR2: "%tr2",
|
||||
TR3: "%tr3",
|
||||
TR4: "%tr4",
|
||||
TR5: "%tr5",
|
||||
TR6: "%tr6",
|
||||
TR7: "%tr7",
|
||||
}
|
||||
|
||||
var gnuOp = map[Op]string{
|
||||
CBW: "cbtw",
|
||||
CDQ: "cltd",
|
||||
CMPSD: "cmpsl",
|
||||
CMPSD_XMM: "cmpsd",
|
||||
CWD: "cwtd",
|
||||
CWDE: "cwtl",
|
||||
CQO: "cqto",
|
||||
INSD: "insl",
|
||||
IRET: "iretw",
|
||||
IRETD: "iret",
|
||||
IRETQ: "iretq",
|
||||
LODSB: "lods",
|
||||
LODSD: "lods",
|
||||
LODSQ: "lods",
|
||||
LODSW: "lods",
|
||||
MOVSD: "movsl",
|
||||
MOVSD_XMM: "movsd",
|
||||
OUTSD: "outsl",
|
||||
POPA: "popaw",
|
||||
POPAD: "popa",
|
||||
POPF: "popfw",
|
||||
POPFD: "popf",
|
||||
PUSHA: "pushaw",
|
||||
PUSHAD: "pusha",
|
||||
PUSHF: "pushfw",
|
||||
PUSHFD: "pushf",
|
||||
SCASB: "scas",
|
||||
SCASD: "scas",
|
||||
SCASQ: "scas",
|
||||
SCASW: "scas",
|
||||
STOSB: "stos",
|
||||
STOSD: "stos",
|
||||
STOSQ: "stos",
|
||||
STOSW: "stos",
|
||||
XLATB: "xlat",
|
||||
}
|
||||
|
||||
var cmppsOps = []string{
|
||||
"cmpeq",
|
||||
"cmplt",
|
||||
"cmple",
|
||||
"cmpunord",
|
||||
"cmpneq",
|
||||
"cmpnlt",
|
||||
"cmpnle",
|
||||
"cmpord",
|
||||
}
|
||||
|
||||
var pclmulqOps = []string{
|
||||
"pclmullqlqdq",
|
||||
"pclmulhqlqdq",
|
||||
"pclmullqhqdq",
|
||||
"pclmulhqhqdq",
|
||||
}
|
||||
|
||||
func countPrefix(inst *Inst, target Prefix) int {
|
||||
n := 0
|
||||
for _, p := range inst.Prefix {
|
||||
if p&0xFF == target&0xFF {
|
||||
n++
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func markLastImplicit(inst *Inst, prefix Prefix) bool {
|
||||
for i := len(inst.Prefix) - 1; i >= 0; i-- {
|
||||
p := inst.Prefix[i]
|
||||
if p&0xFF == prefix {
|
||||
inst.Prefix[i] |= PrefixImplicit
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func unmarkImplicit(inst *Inst, prefix Prefix) {
|
||||
for i := len(inst.Prefix) - 1; i >= 0; i-- {
|
||||
p := inst.Prefix[i]
|
||||
if p&0xFF == prefix {
|
||||
inst.Prefix[i] &^= PrefixImplicit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func byteSizeSuffix(b int) string {
|
||||
switch b {
|
||||
case 1:
|
||||
return "b"
|
||||
case 2:
|
||||
return "w"
|
||||
case 4:
|
||||
return "l"
|
||||
case 8:
|
||||
return "q"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func argBytes(inst *Inst, arg Arg) int {
|
||||
if isMem(arg) {
|
||||
return inst.MemBytes
|
||||
}
|
||||
return regBytes(arg)
|
||||
}
|
||||
|
||||
func isFloat(op Op) bool {
|
||||
switch op {
|
||||
case FADD, FCOM, FCOMP, FDIV, FDIVR, FIADD, FICOM, FICOMP, FIDIV, FIDIVR, FILD, FIMUL, FIST, FISTP, FISTTP, FISUB, FISUBR, FLD, FMUL, FST, FSTP, FSUB, FSUBR:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isFloatInt(op Op) bool {
|
||||
switch op {
|
||||
case FIADD, FICOM, FICOMP, FIDIV, FIDIVR, FILD, FIMUL, FIST, FISTP, FISTTP, FISUB, FISUBR:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,649 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package x86asm implements decoding of x86 machine code.
|
||||
package x86asm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// An Inst is a single instruction.
|
||||
type Inst struct {
|
||||
Prefix Prefixes // Prefixes applied to the instruction.
|
||||
Op Op // Opcode mnemonic
|
||||
Opcode uint32 // Encoded opcode bits, left aligned (first byte is Opcode>>24, etc)
|
||||
Args Args // Instruction arguments, in Intel order
|
||||
Mode int // processor mode in bits: 16, 32, or 64
|
||||
AddrSize int // address size in bits: 16, 32, or 64
|
||||
DataSize int // operand size in bits: 16, 32, or 64
|
||||
MemBytes int // size of memory argument in bytes: 1, 2, 4, 8, 16, and so on.
|
||||
Len int // length of encoded instruction in bytes
|
||||
PCRel int // length of PC-relative address in instruction encoding
|
||||
PCRelOff int // index of start of PC-relative address in instruction encoding
|
||||
}
|
||||
|
||||
// Prefixes is an array of prefixes associated with a single instruction.
|
||||
// The prefixes are listed in the same order as found in the instruction:
|
||||
// each prefix byte corresponds to one slot in the array. The first zero
|
||||
// in the array marks the end of the prefixes.
|
||||
type Prefixes [14]Prefix
|
||||
|
||||
// A Prefix represents an Intel instruction prefix.
|
||||
// The low 8 bits are the actual prefix byte encoding,
|
||||
// and the top 8 bits contain distinguishing bits and metadata.
|
||||
type Prefix uint16
|
||||
|
||||
const (
|
||||
// Metadata about the role of a prefix in an instruction.
|
||||
PrefixImplicit Prefix = 0x8000 // prefix is implied by instruction text
|
||||
PrefixIgnored Prefix = 0x4000 // prefix is ignored: either irrelevant or overridden by a later prefix
|
||||
PrefixInvalid Prefix = 0x2000 // prefix makes entire instruction invalid (bad LOCK)
|
||||
|
||||
// Memory segment overrides.
|
||||
PrefixES Prefix = 0x26 // ES segment override
|
||||
PrefixCS Prefix = 0x2E // CS segment override
|
||||
PrefixSS Prefix = 0x36 // SS segment override
|
||||
PrefixDS Prefix = 0x3E // DS segment override
|
||||
PrefixFS Prefix = 0x64 // FS segment override
|
||||
PrefixGS Prefix = 0x65 // GS segment override
|
||||
|
||||
// Branch prediction.
|
||||
PrefixPN Prefix = 0x12E // predict not taken (conditional branch only)
|
||||
PrefixPT Prefix = 0x13E // predict taken (conditional branch only)
|
||||
|
||||
// Size attributes.
|
||||
PrefixDataSize Prefix = 0x66 // operand size override
|
||||
PrefixData16 Prefix = 0x166
|
||||
PrefixData32 Prefix = 0x266
|
||||
PrefixAddrSize Prefix = 0x67 // address size override
|
||||
PrefixAddr16 Prefix = 0x167
|
||||
PrefixAddr32 Prefix = 0x267
|
||||
|
||||
// One of a kind.
|
||||
PrefixLOCK Prefix = 0xF0 // lock
|
||||
PrefixREPN Prefix = 0xF2 // repeat not zero
|
||||
PrefixXACQUIRE Prefix = 0x1F2
|
||||
PrefixBND Prefix = 0x2F2
|
||||
PrefixREP Prefix = 0xF3 // repeat
|
||||
PrefixXRELEASE Prefix = 0x1F3
|
||||
|
||||
// The REX prefixes must be in the range [PrefixREX, PrefixREX+0x10).
|
||||
// the other bits are set or not according to the intended use.
|
||||
PrefixREX Prefix = 0x40 // REX 64-bit extension prefix
|
||||
PrefixREXW Prefix = 0x08 // extension bit W (64-bit instruction width)
|
||||
PrefixREXR Prefix = 0x04 // extension bit R (r field in modrm)
|
||||
PrefixREXX Prefix = 0x02 // extension bit X (index field in sib)
|
||||
PrefixREXB Prefix = 0x01 // extension bit B (r/m field in modrm or base field in sib)
|
||||
PrefixVEX2Bytes Prefix = 0xC5 // Short form of vex prefix
|
||||
PrefixVEX3Bytes Prefix = 0xC4 // Long form of vex prefix
|
||||
)
|
||||
|
||||
// IsREX reports whether p is a REX prefix byte.
|
||||
func (p Prefix) IsREX() bool {
|
||||
return p&0xF0 == PrefixREX
|
||||
}
|
||||
|
||||
func (p Prefix) IsVEX() bool {
|
||||
return p&0xFF == PrefixVEX2Bytes || p&0xFF == PrefixVEX3Bytes
|
||||
}
|
||||
|
||||
func (p Prefix) String() string {
|
||||
p &^= PrefixImplicit | PrefixIgnored | PrefixInvalid
|
||||
if s := prefixNames[p]; s != "" {
|
||||
return s
|
||||
}
|
||||
|
||||
if p.IsREX() {
|
||||
s := "REX."
|
||||
if p&PrefixREXW != 0 {
|
||||
s += "W"
|
||||
}
|
||||
if p&PrefixREXR != 0 {
|
||||
s += "R"
|
||||
}
|
||||
if p&PrefixREXX != 0 {
|
||||
s += "X"
|
||||
}
|
||||
if p&PrefixREXB != 0 {
|
||||
s += "B"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
return fmt.Sprintf("Prefix(%#x)", int(p))
|
||||
}
|
||||
|
||||
// An Op is an x86 opcode.
|
||||
type Op uint32
|
||||
|
||||
func (op Op) String() string {
|
||||
i := int(op)
|
||||
if i < 0 || i >= len(opNames) || opNames[i] == "" {
|
||||
return fmt.Sprintf("Op(%d)", i)
|
||||
}
|
||||
return opNames[i]
|
||||
}
|
||||
|
||||
// An Args holds the instruction arguments.
|
||||
// If an instruction has fewer than 4 arguments,
|
||||
// the final elements in the array are nil.
|
||||
type Args [4]Arg
|
||||
|
||||
// An Arg is a single instruction argument,
|
||||
// one of these types: Reg, Mem, Imm, Rel.
|
||||
type Arg interface {
|
||||
String() string
|
||||
isArg()
|
||||
}
|
||||
|
||||
// Note that the implements of Arg that follow are all sized
|
||||
// so that on a 64-bit machine the data can be inlined in
|
||||
// the interface value instead of requiring an allocation.
|
||||
|
||||
// A Reg is a single register.
|
||||
// The zero Reg value has no name but indicates ``no register.''
|
||||
type Reg uint8
|
||||
|
||||
const (
|
||||
_ Reg = iota
|
||||
|
||||
// 8-bit
|
||||
AL
|
||||
CL
|
||||
DL
|
||||
BL
|
||||
AH
|
||||
CH
|
||||
DH
|
||||
BH
|
||||
SPB
|
||||
BPB
|
||||
SIB
|
||||
DIB
|
||||
R8B
|
||||
R9B
|
||||
R10B
|
||||
R11B
|
||||
R12B
|
||||
R13B
|
||||
R14B
|
||||
R15B
|
||||
|
||||
// 16-bit
|
||||
AX
|
||||
CX
|
||||
DX
|
||||
BX
|
||||
SP
|
||||
BP
|
||||
SI
|
||||
DI
|
||||
R8W
|
||||
R9W
|
||||
R10W
|
||||
R11W
|
||||
R12W
|
||||
R13W
|
||||
R14W
|
||||
R15W
|
||||
|
||||
// 32-bit
|
||||
EAX
|
||||
ECX
|
||||
EDX
|
||||
EBX
|
||||
ESP
|
||||
EBP
|
||||
ESI
|
||||
EDI
|
||||
R8L
|
||||
R9L
|
||||
R10L
|
||||
R11L
|
||||
R12L
|
||||
R13L
|
||||
R14L
|
||||
R15L
|
||||
|
||||
// 64-bit
|
||||
RAX
|
||||
RCX
|
||||
RDX
|
||||
RBX
|
||||
RSP
|
||||
RBP
|
||||
RSI
|
||||
RDI
|
||||
R8
|
||||
R9
|
||||
R10
|
||||
R11
|
||||
R12
|
||||
R13
|
||||
R14
|
||||
R15
|
||||
|
||||
// Instruction pointer.
|
||||
IP // 16-bit
|
||||
EIP // 32-bit
|
||||
RIP // 64-bit
|
||||
|
||||
// 387 floating point registers.
|
||||
F0
|
||||
F1
|
||||
F2
|
||||
F3
|
||||
F4
|
||||
F5
|
||||
F6
|
||||
F7
|
||||
|
||||
// MMX registers.
|
||||
M0
|
||||
M1
|
||||
M2
|
||||
M3
|
||||
M4
|
||||
M5
|
||||
M6
|
||||
M7
|
||||
|
||||
// XMM registers.
|
||||
X0
|
||||
X1
|
||||
X2
|
||||
X3
|
||||
X4
|
||||
X5
|
||||
X6
|
||||
X7
|
||||
X8
|
||||
X9
|
||||
X10
|
||||
X11
|
||||
X12
|
||||
X13
|
||||
X14
|
||||
X15
|
||||
|
||||
// Segment registers.
|
||||
ES
|
||||
CS
|
||||
SS
|
||||
DS
|
||||
FS
|
||||
GS
|
||||
|
||||
// System registers.
|
||||
GDTR
|
||||
IDTR
|
||||
LDTR
|
||||
MSW
|
||||
TASK
|
||||
|
||||
// Control registers.
|
||||
CR0
|
||||
CR1
|
||||
CR2
|
||||
CR3
|
||||
CR4
|
||||
CR5
|
||||
CR6
|
||||
CR7
|
||||
CR8
|
||||
CR9
|
||||
CR10
|
||||
CR11
|
||||
CR12
|
||||
CR13
|
||||
CR14
|
||||
CR15
|
||||
|
||||
// Debug registers.
|
||||
DR0
|
||||
DR1
|
||||
DR2
|
||||
DR3
|
||||
DR4
|
||||
DR5
|
||||
DR6
|
||||
DR7
|
||||
DR8
|
||||
DR9
|
||||
DR10
|
||||
DR11
|
||||
DR12
|
||||
DR13
|
||||
DR14
|
||||
DR15
|
||||
|
||||
// Task registers.
|
||||
TR0
|
||||
TR1
|
||||
TR2
|
||||
TR3
|
||||
TR4
|
||||
TR5
|
||||
TR6
|
||||
TR7
|
||||
)
|
||||
|
||||
const regMax = TR7
|
||||
|
||||
func (Reg) isArg() {}
|
||||
|
||||
func (r Reg) String() string {
|
||||
i := int(r)
|
||||
if i < 0 || i >= len(regNames) || regNames[i] == "" {
|
||||
return fmt.Sprintf("Reg(%d)", i)
|
||||
}
|
||||
return regNames[i]
|
||||
}
|
||||
|
||||
// A Mem is a memory reference.
|
||||
// The general form is Segment:[Base+Scale*Index+Disp].
|
||||
type Mem struct {
|
||||
Segment Reg
|
||||
Base Reg
|
||||
Scale uint8
|
||||
Index Reg
|
||||
Disp int64
|
||||
}
|
||||
|
||||
func (Mem) isArg() {}
|
||||
|
||||
func (m Mem) String() string {
|
||||
var base, plus, scale, index, disp string
|
||||
|
||||
if m.Base != 0 {
|
||||
base = m.Base.String()
|
||||
}
|
||||
if m.Scale != 0 {
|
||||
if m.Base != 0 {
|
||||
plus = "+"
|
||||
}
|
||||
if m.Scale > 1 {
|
||||
scale = fmt.Sprintf("%d*", m.Scale)
|
||||
}
|
||||
index = m.Index.String()
|
||||
}
|
||||
if m.Disp != 0 || m.Base == 0 && m.Scale == 0 {
|
||||
disp = fmt.Sprintf("%+#x", m.Disp)
|
||||
}
|
||||
return "[" + base + plus + scale + index + disp + "]"
|
||||
}
|
||||
|
||||
// A Rel is an offset relative to the current instruction pointer.
|
||||
type Rel int32
|
||||
|
||||
func (Rel) isArg() {}
|
||||
|
||||
func (r Rel) String() string {
|
||||
return fmt.Sprintf(".%+d", r)
|
||||
}
|
||||
|
||||
// An Imm is an integer constant.
|
||||
type Imm int64
|
||||
|
||||
func (Imm) isArg() {}
|
||||
|
||||
func (i Imm) String() string {
|
||||
return fmt.Sprintf("%#x", int64(i))
|
||||
}
|
||||
|
||||
func (i Inst) String() string {
|
||||
var buf bytes.Buffer
|
||||
for _, p := range i.Prefix {
|
||||
if p == 0 {
|
||||
break
|
||||
}
|
||||
if p&PrefixImplicit != 0 {
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(&buf, "%v ", p)
|
||||
}
|
||||
fmt.Fprintf(&buf, "%v", i.Op)
|
||||
sep := " "
|
||||
for _, v := range i.Args {
|
||||
if v == nil {
|
||||
break
|
||||
}
|
||||
fmt.Fprintf(&buf, "%s%v", sep, v)
|
||||
sep = ", "
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func isReg(a Arg) bool {
|
||||
_, ok := a.(Reg)
|
||||
return ok
|
||||
}
|
||||
|
||||
func isSegReg(a Arg) bool {
|
||||
r, ok := a.(Reg)
|
||||
return ok && ES <= r && r <= GS
|
||||
}
|
||||
|
||||
func isMem(a Arg) bool {
|
||||
_, ok := a.(Mem)
|
||||
return ok
|
||||
}
|
||||
|
||||
func isImm(a Arg) bool {
|
||||
_, ok := a.(Imm)
|
||||
return ok
|
||||
}
|
||||
|
||||
func regBytes(a Arg) int {
|
||||
r, ok := a.(Reg)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
if AL <= r && r <= R15B {
|
||||
return 1
|
||||
}
|
||||
if AX <= r && r <= R15W {
|
||||
return 2
|
||||
}
|
||||
if EAX <= r && r <= R15L {
|
||||
return 4
|
||||
}
|
||||
if RAX <= r && r <= R15 {
|
||||
return 8
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func isSegment(p Prefix) bool {
|
||||
switch p {
|
||||
case PrefixCS, PrefixDS, PrefixES, PrefixFS, PrefixGS, PrefixSS:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// The Op definitions and string list are in tables.go.
|
||||
|
||||
var prefixNames = map[Prefix]string{
|
||||
PrefixCS: "CS",
|
||||
PrefixDS: "DS",
|
||||
PrefixES: "ES",
|
||||
PrefixFS: "FS",
|
||||
PrefixGS: "GS",
|
||||
PrefixSS: "SS",
|
||||
PrefixLOCK: "LOCK",
|
||||
PrefixREP: "REP",
|
||||
PrefixREPN: "REPN",
|
||||
PrefixAddrSize: "ADDRSIZE",
|
||||
PrefixDataSize: "DATASIZE",
|
||||
PrefixAddr16: "ADDR16",
|
||||
PrefixData16: "DATA16",
|
||||
PrefixAddr32: "ADDR32",
|
||||
PrefixData32: "DATA32",
|
||||
PrefixBND: "BND",
|
||||
PrefixXACQUIRE: "XACQUIRE",
|
||||
PrefixXRELEASE: "XRELEASE",
|
||||
PrefixREX: "REX",
|
||||
PrefixPT: "PT",
|
||||
PrefixPN: "PN",
|
||||
}
|
||||
|
||||
var regNames = [...]string{
|
||||
AL: "AL",
|
||||
CL: "CL",
|
||||
BL: "BL",
|
||||
DL: "DL",
|
||||
AH: "AH",
|
||||
CH: "CH",
|
||||
BH: "BH",
|
||||
DH: "DH",
|
||||
SPB: "SPB",
|
||||
BPB: "BPB",
|
||||
SIB: "SIB",
|
||||
DIB: "DIB",
|
||||
R8B: "R8B",
|
||||
R9B: "R9B",
|
||||
R10B: "R10B",
|
||||
R11B: "R11B",
|
||||
R12B: "R12B",
|
||||
R13B: "R13B",
|
||||
R14B: "R14B",
|
||||
R15B: "R15B",
|
||||
AX: "AX",
|
||||
CX: "CX",
|
||||
BX: "BX",
|
||||
DX: "DX",
|
||||
SP: "SP",
|
||||
BP: "BP",
|
||||
SI: "SI",
|
||||
DI: "DI",
|
||||
R8W: "R8W",
|
||||
R9W: "R9W",
|
||||
R10W: "R10W",
|
||||
R11W: "R11W",
|
||||
R12W: "R12W",
|
||||
R13W: "R13W",
|
||||
R14W: "R14W",
|
||||
R15W: "R15W",
|
||||
EAX: "EAX",
|
||||
ECX: "ECX",
|
||||
EDX: "EDX",
|
||||
EBX: "EBX",
|
||||
ESP: "ESP",
|
||||
EBP: "EBP",
|
||||
ESI: "ESI",
|
||||
EDI: "EDI",
|
||||
R8L: "R8L",
|
||||
R9L: "R9L",
|
||||
R10L: "R10L",
|
||||
R11L: "R11L",
|
||||
R12L: "R12L",
|
||||
R13L: "R13L",
|
||||
R14L: "R14L",
|
||||
R15L: "R15L",
|
||||
RAX: "RAX",
|
||||
RCX: "RCX",
|
||||
RDX: "RDX",
|
||||
RBX: "RBX",
|
||||
RSP: "RSP",
|
||||
RBP: "RBP",
|
||||
RSI: "RSI",
|
||||
RDI: "RDI",
|
||||
R8: "R8",
|
||||
R9: "R9",
|
||||
R10: "R10",
|
||||
R11: "R11",
|
||||
R12: "R12",
|
||||
R13: "R13",
|
||||
R14: "R14",
|
||||
R15: "R15",
|
||||
IP: "IP",
|
||||
EIP: "EIP",
|
||||
RIP: "RIP",
|
||||
F0: "F0",
|
||||
F1: "F1",
|
||||
F2: "F2",
|
||||
F3: "F3",
|
||||
F4: "F4",
|
||||
F5: "F5",
|
||||
F6: "F6",
|
||||
F7: "F7",
|
||||
M0: "M0",
|
||||
M1: "M1",
|
||||
M2: "M2",
|
||||
M3: "M3",
|
||||
M4: "M4",
|
||||
M5: "M5",
|
||||
M6: "M6",
|
||||
M7: "M7",
|
||||
X0: "X0",
|
||||
X1: "X1",
|
||||
X2: "X2",
|
||||
X3: "X3",
|
||||
X4: "X4",
|
||||
X5: "X5",
|
||||
X6: "X6",
|
||||
X7: "X7",
|
||||
X8: "X8",
|
||||
X9: "X9",
|
||||
X10: "X10",
|
||||
X11: "X11",
|
||||
X12: "X12",
|
||||
X13: "X13",
|
||||
X14: "X14",
|
||||
X15: "X15",
|
||||
CS: "CS",
|
||||
SS: "SS",
|
||||
DS: "DS",
|
||||
ES: "ES",
|
||||
FS: "FS",
|
||||
GS: "GS",
|
||||
GDTR: "GDTR",
|
||||
IDTR: "IDTR",
|
||||
LDTR: "LDTR",
|
||||
MSW: "MSW",
|
||||
TASK: "TASK",
|
||||
CR0: "CR0",
|
||||
CR1: "CR1",
|
||||
CR2: "CR2",
|
||||
CR3: "CR3",
|
||||
CR4: "CR4",
|
||||
CR5: "CR5",
|
||||
CR6: "CR6",
|
||||
CR7: "CR7",
|
||||
CR8: "CR8",
|
||||
CR9: "CR9",
|
||||
CR10: "CR10",
|
||||
CR11: "CR11",
|
||||
CR12: "CR12",
|
||||
CR13: "CR13",
|
||||
CR14: "CR14",
|
||||
CR15: "CR15",
|
||||
DR0: "DR0",
|
||||
DR1: "DR1",
|
||||
DR2: "DR2",
|
||||
DR3: "DR3",
|
||||
DR4: "DR4",
|
||||
DR5: "DR5",
|
||||
DR6: "DR6",
|
||||
DR7: "DR7",
|
||||
DR8: "DR8",
|
||||
DR9: "DR9",
|
||||
DR10: "DR10",
|
||||
DR11: "DR11",
|
||||
DR12: "DR12",
|
||||
DR13: "DR13",
|
||||
DR14: "DR14",
|
||||
DR15: "DR15",
|
||||
TR0: "TR0",
|
||||
TR1: "TR1",
|
||||
TR2: "TR2",
|
||||
TR3: "TR3",
|
||||
TR4: "TR4",
|
||||
TR5: "TR5",
|
||||
TR6: "TR6",
|
||||
TR7: "TR7",
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package x86asm
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRegString(t *testing.T) {
|
||||
for r := Reg(1); r <= regMax; r++ {
|
||||
if regNames[r] == "" {
|
||||
t.Errorf("regNames[%d] is missing", int(r))
|
||||
} else if s := r.String(); strings.Contains(s, "Reg(") {
|
||||
t.Errorf("Reg(%d).String() = %s, want proper name", int(r), s)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,560 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package x86asm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// IntelSyntax returns the Intel assembler syntax for the instruction, as defined by Intel's XED tool.
|
||||
func IntelSyntax(inst Inst, pc uint64, symname SymLookup) string {
|
||||
if symname == nil {
|
||||
symname = func(uint64) (string, uint64) { return "", 0 }
|
||||
}
|
||||
|
||||
var iargs []Arg
|
||||
for _, a := range inst.Args {
|
||||
if a == nil {
|
||||
break
|
||||
}
|
||||
iargs = append(iargs, a)
|
||||
}
|
||||
|
||||
switch inst.Op {
|
||||
case INSB, INSD, INSW, OUTSB, OUTSD, OUTSW, LOOPNE, JCXZ, JECXZ, JRCXZ, LOOP, LOOPE, MOV, XLATB:
|
||||
if inst.Op == MOV && (inst.Opcode>>16)&0xFFFC != 0x0F20 {
|
||||
break
|
||||
}
|
||||
for i, p := range inst.Prefix {
|
||||
if p&0xFF == PrefixAddrSize {
|
||||
inst.Prefix[i] &^= PrefixImplicit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch inst.Op {
|
||||
case MOV:
|
||||
dst, _ := inst.Args[0].(Reg)
|
||||
src, _ := inst.Args[1].(Reg)
|
||||
if ES <= dst && dst <= GS && EAX <= src && src <= R15L {
|
||||
src -= EAX - AX
|
||||
iargs[1] = src
|
||||
}
|
||||
if ES <= dst && dst <= GS && RAX <= src && src <= R15 {
|
||||
src -= RAX - AX
|
||||
iargs[1] = src
|
||||
}
|
||||
|
||||
if inst.Opcode>>24&^3 == 0xA0 {
|
||||
for i, p := range inst.Prefix {
|
||||
if p&0xFF == PrefixAddrSize {
|
||||
inst.Prefix[i] |= PrefixImplicit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch inst.Op {
|
||||
case AAM, AAD:
|
||||
if imm, ok := iargs[0].(Imm); ok {
|
||||
if inst.DataSize == 32 {
|
||||
iargs[0] = Imm(uint32(int8(imm)))
|
||||
} else if inst.DataSize == 16 {
|
||||
iargs[0] = Imm(uint16(int8(imm)))
|
||||
}
|
||||
}
|
||||
|
||||
case PUSH:
|
||||
if imm, ok := iargs[0].(Imm); ok {
|
||||
iargs[0] = Imm(uint32(imm))
|
||||
}
|
||||
}
|
||||
|
||||
for _, p := range inst.Prefix {
|
||||
if p&PrefixImplicit != 0 {
|
||||
for j, pj := range inst.Prefix {
|
||||
if pj&0xFF == p&0xFF {
|
||||
inst.Prefix[j] |= PrefixImplicit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if inst.Op != 0 {
|
||||
for i, p := range inst.Prefix {
|
||||
switch p &^ PrefixIgnored {
|
||||
case PrefixData16, PrefixData32, PrefixCS, PrefixDS, PrefixES, PrefixSS:
|
||||
inst.Prefix[i] |= PrefixImplicit
|
||||
}
|
||||
if p.IsREX() {
|
||||
inst.Prefix[i] |= PrefixImplicit
|
||||
}
|
||||
if p.IsVEX() {
|
||||
if p == PrefixVEX3Bytes {
|
||||
inst.Prefix[i+2] |= PrefixImplicit
|
||||
}
|
||||
inst.Prefix[i] |= PrefixImplicit
|
||||
inst.Prefix[i+1] |= PrefixImplicit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isLoop[inst.Op] || inst.Op == JCXZ || inst.Op == JECXZ || inst.Op == JRCXZ {
|
||||
for i, p := range inst.Prefix {
|
||||
if p == PrefixPT || p == PrefixPN {
|
||||
inst.Prefix[i] |= PrefixImplicit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch inst.Op {
|
||||
case AAA, AAS, CBW, CDQE, CLC, CLD, CLI, CLTS, CMC, CPUID, CQO, CWD, DAA, DAS,
|
||||
FDECSTP, FINCSTP, FNCLEX, FNINIT, FNOP, FWAIT, HLT,
|
||||
ICEBP, INSB, INSD, INSW, INT, INTO, INVD, IRET, IRETQ,
|
||||
LAHF, LEAVE, LRET, MONITOR, MWAIT, NOP, OUTSB, OUTSD, OUTSW,
|
||||
PAUSE, POPA, POPF, POPFQ, PUSHA, PUSHF, PUSHFQ,
|
||||
RDMSR, RDPMC, RDTSC, RDTSCP, RET, RSM,
|
||||
SAHF, STC, STD, STI, SYSENTER, SYSEXIT, SYSRET,
|
||||
UD2, WBINVD, WRMSR, XEND, XLATB, XTEST:
|
||||
|
||||
if inst.Op == NOP && inst.Opcode>>24 != 0x90 {
|
||||
break
|
||||
}
|
||||
if inst.Op == RET && inst.Opcode>>24 != 0xC3 {
|
||||
break
|
||||
}
|
||||
if inst.Op == INT && inst.Opcode>>24 != 0xCC {
|
||||
break
|
||||
}
|
||||
if inst.Op == LRET && inst.Opcode>>24 != 0xcb {
|
||||
break
|
||||
}
|
||||
for i, p := range inst.Prefix {
|
||||
if p&0xFF == PrefixDataSize {
|
||||
inst.Prefix[i] &^= PrefixImplicit | PrefixIgnored
|
||||
}
|
||||
}
|
||||
|
||||
case 0:
|
||||
// ok
|
||||
}
|
||||
|
||||
switch inst.Op {
|
||||
case INSB, INSD, INSW, OUTSB, OUTSD, OUTSW, MONITOR, MWAIT, XLATB:
|
||||
iargs = nil
|
||||
|
||||
case STOSB, STOSW, STOSD, STOSQ:
|
||||
iargs = iargs[:1]
|
||||
|
||||
case LODSB, LODSW, LODSD, LODSQ, SCASB, SCASW, SCASD, SCASQ:
|
||||
iargs = iargs[1:]
|
||||
}
|
||||
|
||||
const (
|
||||
haveData16 = 1 << iota
|
||||
haveData32
|
||||
haveAddr16
|
||||
haveAddr32
|
||||
haveXacquire
|
||||
haveXrelease
|
||||
haveLock
|
||||
haveHintTaken
|
||||
haveHintNotTaken
|
||||
haveBnd
|
||||
)
|
||||
var prefixBits uint32
|
||||
prefix := ""
|
||||
for _, p := range inst.Prefix {
|
||||
if p == 0 {
|
||||
break
|
||||
}
|
||||
if p&0xFF == 0xF3 {
|
||||
prefixBits &^= haveBnd
|
||||
}
|
||||
if p&(PrefixImplicit|PrefixIgnored) != 0 {
|
||||
continue
|
||||
}
|
||||
switch p {
|
||||
default:
|
||||
prefix += strings.ToLower(p.String()) + " "
|
||||
case PrefixCS, PrefixDS, PrefixES, PrefixFS, PrefixGS, PrefixSS:
|
||||
if inst.Op == 0 {
|
||||
prefix += strings.ToLower(p.String()) + " "
|
||||
}
|
||||
case PrefixREPN:
|
||||
prefix += "repne "
|
||||
case PrefixLOCK:
|
||||
prefixBits |= haveLock
|
||||
case PrefixData16, PrefixDataSize:
|
||||
prefixBits |= haveData16
|
||||
case PrefixData32:
|
||||
prefixBits |= haveData32
|
||||
case PrefixAddrSize, PrefixAddr16:
|
||||
prefixBits |= haveAddr16
|
||||
case PrefixAddr32:
|
||||
prefixBits |= haveAddr32
|
||||
case PrefixXACQUIRE:
|
||||
prefixBits |= haveXacquire
|
||||
case PrefixXRELEASE:
|
||||
prefixBits |= haveXrelease
|
||||
case PrefixPT:
|
||||
prefixBits |= haveHintTaken
|
||||
case PrefixPN:
|
||||
prefixBits |= haveHintNotTaken
|
||||
case PrefixBND:
|
||||
prefixBits |= haveBnd
|
||||
}
|
||||
}
|
||||
switch inst.Op {
|
||||
case JMP:
|
||||
if inst.Opcode>>24 == 0xEB {
|
||||
prefixBits &^= haveBnd
|
||||
}
|
||||
case RET, LRET:
|
||||
prefixBits &^= haveData16 | haveData32
|
||||
}
|
||||
|
||||
if prefixBits&haveXacquire != 0 {
|
||||
prefix += "xacquire "
|
||||
}
|
||||
if prefixBits&haveXrelease != 0 {
|
||||
prefix += "xrelease "
|
||||
}
|
||||
if prefixBits&haveLock != 0 {
|
||||
prefix += "lock "
|
||||
}
|
||||
if prefixBits&haveBnd != 0 {
|
||||
prefix += "bnd "
|
||||
}
|
||||
if prefixBits&haveHintTaken != 0 {
|
||||
prefix += "hint-taken "
|
||||
}
|
||||
if prefixBits&haveHintNotTaken != 0 {
|
||||
prefix += "hint-not-taken "
|
||||
}
|
||||
if prefixBits&haveAddr16 != 0 {
|
||||
prefix += "addr16 "
|
||||
}
|
||||
if prefixBits&haveAddr32 != 0 {
|
||||
prefix += "addr32 "
|
||||
}
|
||||
if prefixBits&haveData16 != 0 {
|
||||
prefix += "data16 "
|
||||
}
|
||||
if prefixBits&haveData32 != 0 {
|
||||
prefix += "data32 "
|
||||
}
|
||||
|
||||
if inst.Op == 0 {
|
||||
if prefix == "" {
|
||||
return "<no instruction>"
|
||||
}
|
||||
return prefix[:len(prefix)-1]
|
||||
}
|
||||
|
||||
var args []string
|
||||
for _, a := range iargs {
|
||||
if a == nil {
|
||||
break
|
||||
}
|
||||
args = append(args, intelArg(&inst, pc, symname, a))
|
||||
}
|
||||
|
||||
var op string
|
||||
switch inst.Op {
|
||||
case NOP:
|
||||
if inst.Opcode>>24 == 0x0F {
|
||||
if inst.DataSize == 16 {
|
||||
args = append(args, "ax")
|
||||
} else {
|
||||
args = append(args, "eax")
|
||||
}
|
||||
}
|
||||
|
||||
case BLENDVPD, BLENDVPS, PBLENDVB:
|
||||
args = args[:2]
|
||||
|
||||
case INT:
|
||||
if inst.Opcode>>24 == 0xCC {
|
||||
args = nil
|
||||
op = "int3"
|
||||
}
|
||||
|
||||
case LCALL, LJMP:
|
||||
if len(args) == 2 {
|
||||
args[0], args[1] = args[1], args[0]
|
||||
}
|
||||
|
||||
case FCHS, FABS, FTST, FLDPI, FLDL2E, FLDLG2, F2XM1, FXAM, FLD1, FLDL2T, FSQRT, FRNDINT, FCOS, FSIN:
|
||||
if len(args) == 0 {
|
||||
args = append(args, "st0")
|
||||
}
|
||||
|
||||
case FPTAN, FSINCOS, FUCOMPP, FCOMPP, FYL2X, FPATAN, FXTRACT, FPREM1, FPREM, FYL2XP1, FSCALE:
|
||||
if len(args) == 0 {
|
||||
args = []string{"st0", "st1"}
|
||||
}
|
||||
|
||||
case FST, FSTP, FISTTP, FIST, FISTP, FBSTP:
|
||||
if len(args) == 1 {
|
||||
args = append(args, "st0")
|
||||
}
|
||||
|
||||
case FLD, FXCH, FCOM, FCOMP, FIADD, FIMUL, FICOM, FICOMP, FISUBR, FIDIV, FUCOM, FUCOMP, FILD, FBLD, FADD, FMUL, FSUB, FSUBR, FISUB, FDIV, FDIVR, FIDIVR:
|
||||
if len(args) == 1 {
|
||||
args = []string{"st0", args[0]}
|
||||
}
|
||||
|
||||
case MASKMOVDQU, MASKMOVQ, XLATB, OUTSB, OUTSW, OUTSD:
|
||||
FixSegment:
|
||||
for i := len(inst.Prefix) - 1; i >= 0; i-- {
|
||||
p := inst.Prefix[i] & 0xFF
|
||||
switch p {
|
||||
case PrefixCS, PrefixES, PrefixFS, PrefixGS, PrefixSS:
|
||||
if inst.Mode != 64 || p == PrefixFS || p == PrefixGS {
|
||||
args = append(args, strings.ToLower((inst.Prefix[i] & 0xFF).String()))
|
||||
break FixSegment
|
||||
}
|
||||
case PrefixDS:
|
||||
if inst.Mode != 64 {
|
||||
break FixSegment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if op == "" {
|
||||
op = intelOp[inst.Op]
|
||||
}
|
||||
if op == "" {
|
||||
op = strings.ToLower(inst.Op.String())
|
||||
}
|
||||
if args != nil {
|
||||
op += " " + strings.Join(args, ", ")
|
||||
}
|
||||
return prefix + op
|
||||
}
|
||||
|
||||
func intelArg(inst *Inst, pc uint64, symname SymLookup, arg Arg) string {
|
||||
switch a := arg.(type) {
|
||||
case Imm:
|
||||
if s, base := symname(uint64(a)); s != "" {
|
||||
suffix := ""
|
||||
if uint64(a) != base {
|
||||
suffix = fmt.Sprintf("%+d", uint64(a)-base)
|
||||
}
|
||||
return fmt.Sprintf("$%s%s", s, suffix)
|
||||
}
|
||||
if inst.Mode == 32 {
|
||||
return fmt.Sprintf("%#x", uint32(a))
|
||||
}
|
||||
if Imm(int32(a)) == a {
|
||||
return fmt.Sprintf("%#x", int64(a))
|
||||
}
|
||||
return fmt.Sprintf("%#x", uint64(a))
|
||||
case Mem:
|
||||
if a.Base == EIP {
|
||||
a.Base = RIP
|
||||
}
|
||||
prefix := ""
|
||||
switch inst.MemBytes {
|
||||
case 1:
|
||||
prefix = "byte "
|
||||
case 2:
|
||||
prefix = "word "
|
||||
case 4:
|
||||
prefix = "dword "
|
||||
case 8:
|
||||
prefix = "qword "
|
||||
case 16:
|
||||
prefix = "xmmword "
|
||||
case 32:
|
||||
prefix = "ymmword "
|
||||
}
|
||||
switch inst.Op {
|
||||
case INVLPG:
|
||||
prefix = "byte "
|
||||
case STOSB, MOVSB, CMPSB, LODSB, SCASB:
|
||||
prefix = "byte "
|
||||
case STOSW, MOVSW, CMPSW, LODSW, SCASW:
|
||||
prefix = "word "
|
||||
case STOSD, MOVSD, CMPSD, LODSD, SCASD:
|
||||
prefix = "dword "
|
||||
case STOSQ, MOVSQ, CMPSQ, LODSQ, SCASQ:
|
||||
prefix = "qword "
|
||||
case LAR:
|
||||
prefix = "word "
|
||||
case BOUND:
|
||||
if inst.Mode == 32 {
|
||||
prefix = "qword "
|
||||
} else {
|
||||
prefix = "dword "
|
||||
}
|
||||
case PREFETCHW, PREFETCHNTA, PREFETCHT0, PREFETCHT1, PREFETCHT2, CLFLUSH:
|
||||
prefix = "zmmword "
|
||||
}
|
||||
switch inst.Op {
|
||||
case MOVSB, MOVSW, MOVSD, MOVSQ, CMPSB, CMPSW, CMPSD, CMPSQ, STOSB, STOSW, STOSD, STOSQ, SCASB, SCASW, SCASD, SCASQ, LODSB, LODSW, LODSD, LODSQ:
|
||||
switch a.Base {
|
||||
case DI, EDI, RDI:
|
||||
if a.Segment == ES {
|
||||
a.Segment = 0
|
||||
}
|
||||
case SI, ESI, RSI:
|
||||
if a.Segment == DS {
|
||||
a.Segment = 0
|
||||
}
|
||||
}
|
||||
case LEA:
|
||||
a.Segment = 0
|
||||
default:
|
||||
switch a.Base {
|
||||
case SP, ESP, RSP, BP, EBP, RBP:
|
||||
if a.Segment == SS {
|
||||
a.Segment = 0
|
||||
}
|
||||
default:
|
||||
if a.Segment == DS {
|
||||
a.Segment = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if inst.Mode == 64 && a.Segment != FS && a.Segment != GS {
|
||||
a.Segment = 0
|
||||
}
|
||||
|
||||
prefix += "ptr "
|
||||
if s, disp := memArgToSymbol(a, pc, inst.Len, symname); s != "" {
|
||||
suffix := ""
|
||||
if disp != 0 {
|
||||
suffix = fmt.Sprintf("%+d", disp)
|
||||
}
|
||||
return prefix + fmt.Sprintf("[%s%s]", s, suffix)
|
||||
}
|
||||
if a.Segment != 0 {
|
||||
prefix += strings.ToLower(a.Segment.String()) + ":"
|
||||
}
|
||||
prefix += "["
|
||||
if a.Base != 0 {
|
||||
prefix += intelArg(inst, pc, symname, a.Base)
|
||||
}
|
||||
if a.Scale != 0 && a.Index != 0 {
|
||||
if a.Base != 0 {
|
||||
prefix += "+"
|
||||
}
|
||||
prefix += fmt.Sprintf("%s*%d", intelArg(inst, pc, symname, a.Index), a.Scale)
|
||||
}
|
||||
if a.Disp != 0 {
|
||||
if prefix[len(prefix)-1] == '[' && (a.Disp >= 0 || int64(int32(a.Disp)) != a.Disp) {
|
||||
prefix += fmt.Sprintf("%#x", uint64(a.Disp))
|
||||
} else {
|
||||
prefix += fmt.Sprintf("%+#x", a.Disp)
|
||||
}
|
||||
}
|
||||
prefix += "]"
|
||||
return prefix
|
||||
case Rel:
|
||||
if pc == 0 {
|
||||
return fmt.Sprintf(".%+#x", int64(a))
|
||||
} else {
|
||||
addr := pc + uint64(inst.Len) + uint64(a)
|
||||
if s, base := symname(addr); s != "" && addr == base {
|
||||
return fmt.Sprintf("%s", s)
|
||||
} else {
|
||||
addr := pc + uint64(inst.Len) + uint64(a)
|
||||
return fmt.Sprintf("%#x", addr)
|
||||
}
|
||||
}
|
||||
case Reg:
|
||||
if int(a) < len(intelReg) && intelReg[a] != "" {
|
||||
switch inst.Op {
|
||||
case VMOVDQA, VMOVDQU, VMOVNTDQA, VMOVNTDQ:
|
||||
return strings.Replace(intelReg[a], "xmm", "ymm", -1)
|
||||
default:
|
||||
return intelReg[a]
|
||||
}
|
||||
}
|
||||
}
|
||||
return strings.ToLower(arg.String())
|
||||
}
|
||||
|
||||
var intelOp = map[Op]string{
|
||||
JAE: "jnb",
|
||||
JA: "jnbe",
|
||||
JGE: "jnl",
|
||||
JNE: "jnz",
|
||||
JG: "jnle",
|
||||
JE: "jz",
|
||||
SETAE: "setnb",
|
||||
SETA: "setnbe",
|
||||
SETGE: "setnl",
|
||||
SETNE: "setnz",
|
||||
SETG: "setnle",
|
||||
SETE: "setz",
|
||||
CMOVAE: "cmovnb",
|
||||
CMOVA: "cmovnbe",
|
||||
CMOVGE: "cmovnl",
|
||||
CMOVNE: "cmovnz",
|
||||
CMOVG: "cmovnle",
|
||||
CMOVE: "cmovz",
|
||||
LCALL: "call far",
|
||||
LJMP: "jmp far",
|
||||
LRET: "ret far",
|
||||
ICEBP: "int1",
|
||||
MOVSD_XMM: "movsd",
|
||||
XLATB: "xlat",
|
||||
}
|
||||
|
||||
var intelReg = [...]string{
|
||||
F0: "st0",
|
||||
F1: "st1",
|
||||
F2: "st2",
|
||||
F3: "st3",
|
||||
F4: "st4",
|
||||
F5: "st5",
|
||||
F6: "st6",
|
||||
F7: "st7",
|
||||
M0: "mmx0",
|
||||
M1: "mmx1",
|
||||
M2: "mmx2",
|
||||
M3: "mmx3",
|
||||
M4: "mmx4",
|
||||
M5: "mmx5",
|
||||
M6: "mmx6",
|
||||
M7: "mmx7",
|
||||
X0: "xmm0",
|
||||
X1: "xmm1",
|
||||
X2: "xmm2",
|
||||
X3: "xmm3",
|
||||
X4: "xmm4",
|
||||
X5: "xmm5",
|
||||
X6: "xmm6",
|
||||
X7: "xmm7",
|
||||
X8: "xmm8",
|
||||
X9: "xmm9",
|
||||
X10: "xmm10",
|
||||
X11: "xmm11",
|
||||
X12: "xmm12",
|
||||
X13: "xmm13",
|
||||
X14: "xmm14",
|
||||
X15: "xmm15",
|
||||
|
||||
// TODO: Maybe the constants are named wrong.
|
||||
SPB: "spl",
|
||||
BPB: "bpl",
|
||||
SIB: "sil",
|
||||
DIB: "dil",
|
||||
|
||||
R8L: "r8d",
|
||||
R9L: "r9d",
|
||||
R10L: "r10d",
|
||||
R11L: "r11d",
|
||||
R12L: "r12d",
|
||||
R13L: "r13d",
|
||||
R14L: "r14d",
|
||||
R15L: "r15d",
|
||||
}
|
|
@ -0,0 +1,384 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package x86asm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestObjdump32Manual(t *testing.T) { testObjdump32(t, hexCases(t, objdumpManualTests)) }
|
||||
func TestObjdump32Testdata(t *testing.T) { testObjdump32(t, concat(basicPrefixes, testdataCases(t))) }
|
||||
func TestObjdump32ModRM(t *testing.T) { testObjdump32(t, concat(basicPrefixes, enumModRM)) }
|
||||
func TestObjdump32OneByte(t *testing.T) { testBasic(t, testObjdump32) }
|
||||
func TestObjdump320F(t *testing.T) { testBasic(t, testObjdump32, 0x0F) }
|
||||
func TestObjdump320F38(t *testing.T) { testBasic(t, testObjdump32, 0x0F, 0x38) }
|
||||
func TestObjdump320F3A(t *testing.T) { testBasic(t, testObjdump32, 0x0F, 0x3A) }
|
||||
func TestObjdump32Prefix(t *testing.T) { testPrefix(t, testObjdump32) }
|
||||
|
||||
func TestObjdump64Manual(t *testing.T) { testObjdump64(t, hexCases(t, objdumpManualTests)) }
|
||||
func TestObjdump64Testdata(t *testing.T) { testObjdump64(t, concat(basicPrefixes, testdataCases(t))) }
|
||||
func TestObjdump64ModRM(t *testing.T) { testObjdump64(t, concat(basicPrefixes, enumModRM)) }
|
||||
func TestObjdump64OneByte(t *testing.T) { testBasic(t, testObjdump64) }
|
||||
func TestObjdump640F(t *testing.T) { testBasic(t, testObjdump64, 0x0F) }
|
||||
func TestObjdump640F38(t *testing.T) { testBasic(t, testObjdump64, 0x0F, 0x38) }
|
||||
func TestObjdump640F3A(t *testing.T) { testBasic(t, testObjdump64, 0x0F, 0x3A) }
|
||||
func TestObjdump64Prefix(t *testing.T) { testPrefix(t, testObjdump64) }
|
||||
|
||||
func TestObjdump64REXTestdata(t *testing.T) {
|
||||
testObjdump64(t, filter(concat3(basicPrefixes, rexPrefixes, testdataCases(t)), isValidREX))
|
||||
}
|
||||
func TestObjdump64REXModRM(t *testing.T) {
|
||||
testObjdump64(t, concat3(basicPrefixes, rexPrefixes, enumModRM))
|
||||
}
|
||||
func TestObjdump64REXOneByte(t *testing.T) { testBasicREX(t, testObjdump64) }
|
||||
func TestObjdump64REX0F(t *testing.T) { testBasicREX(t, testObjdump64, 0x0F) }
|
||||
func TestObjdump64REX0F38(t *testing.T) { testBasicREX(t, testObjdump64, 0x0F, 0x38) }
|
||||
func TestObjdump64REX0F3A(t *testing.T) { testBasicREX(t, testObjdump64, 0x0F, 0x3A) }
|
||||
func TestObjdump64REXPrefix(t *testing.T) { testPrefixREX(t, testObjdump64) }
|
||||
|
||||
// objdumpManualTests holds test cases that will be run by TestObjdumpManual.
|
||||
// If you are debugging a few cases that turned up in a longer run, it can be useful
|
||||
// to list them here and then use -run=ObjdumpManual, particularly with tracing enabled.
|
||||
var objdumpManualTests = `
|
||||
4883FE017413
|
||||
488DFC2500000000
|
||||
488D3D00000000
|
||||
`
|
||||
|
||||
// allowedMismatchObjdump reports whether the mismatch between text and dec
|
||||
// should be allowed by the test.
|
||||
func allowedMismatchObjdump(text string, size int, inst *Inst, dec ExtInst) bool {
|
||||
if size == 15 && dec.nenc == 15 && contains(text, "truncated") && contains(dec.text, "(bad)") {
|
||||
return true
|
||||
}
|
||||
|
||||
if i := strings.LastIndex(dec.text, " "); isPrefix(dec.text[i+1:]) && size == 1 && isPrefix(text) {
|
||||
return true
|
||||
}
|
||||
|
||||
if size == dec.nenc && contains(dec.text, "movupd") && contains(dec.text, "data32") {
|
||||
s := strings.Replace(dec.text, "data32 ", "", -1)
|
||||
if text == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Simplify our invalid instruction text.
|
||||
if text == "error: unrecognized instruction" {
|
||||
text = "BAD"
|
||||
}
|
||||
|
||||
// Invalid instructions for which libopcodes prints %? register.
|
||||
// FF E8 11 22 33 44:
|
||||
// Invalid instructions for which libopcodes prints "internal disassembler error".
|
||||
// Invalid instructions for which libopcodes prints 8087 only (e.g., DB E0)
|
||||
// or prints 287 only (e.g., DB E4).
|
||||
if contains(dec.text, "%?", "<internal disassembler error>", "(8087 only)", "(287 only)") {
|
||||
dec.text = "(bad)"
|
||||
}
|
||||
|
||||
// 0F 19 11, 0F 1C 11, 0F 1D 11, 0F 1E 11, 0F 1F 11: libopcodes says nop,
|
||||
// but the Intel manuals say that the only NOP there is 0F 1F /0.
|
||||
// Perhaps libopcodes is reporting an older encoding.
|
||||
i := bytes.IndexByte(dec.enc[:], 0x0F)
|
||||
if contains(dec.text, "nop") && i >= 0 && i+2 < len(dec.enc) && dec.enc[i+1]&^7 == 0x18 && (dec.enc[i+1] != 0x1F || (dec.enc[i+2]>>3)&7 != 0) {
|
||||
dec.text = "(bad)"
|
||||
}
|
||||
|
||||
// Any invalid instruction.
|
||||
if text == "BAD" && contains(dec.text, "(bad)") {
|
||||
return true
|
||||
}
|
||||
|
||||
// Instructions libopcodes knows but we do not (e.g., 0F 19 11).
|
||||
if (text == "BAD" || size == 1 && isPrefix(text)) && hasPrefix(dec.text, unsupported...) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Instructions we know but libopcodes does not (e.g., 0F D0 11).
|
||||
if (contains(dec.text, "(bad)") || dec.nenc == 1 && isPrefix(dec.text)) && hasPrefix(text, libopcodesUnsupported...) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Libopcodes rejects F2 90 as NOP. Not sure why.
|
||||
if (contains(dec.text, "(bad)") || dec.nenc == 1 && isPrefix(dec.text)) && inst.Opcode>>24 == 0x90 && countPrefix(inst, 0xF2) > 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
// 0F 20 11, 0F 21 11, 0F 22 11, 0F 23 11, 0F 24 11:
|
||||
// Moves into and out of some control registers seem to be unsupported by libopcodes.
|
||||
// TODO(rsc): Are they invalid somehow?
|
||||
if (contains(dec.text, "(bad)") || dec.nenc == 1 && isPrefix(dec.text)) && contains(text, "%cr", "%db", "%tr") {
|
||||
return true
|
||||
}
|
||||
|
||||
if contains(dec.text, "fwait") && dec.nenc == 1 && dec.enc[0] != 0x9B {
|
||||
return true
|
||||
}
|
||||
|
||||
// 9B D9 11: libopcodes reports FSTSW instead of FWAIT + FNSTSW.
|
||||
// This is correct in that FSTSW is a pseudo-op for the pair, but it really
|
||||
// is a pair of instructions: execution can stop between them.
|
||||
// Our decoder chooses to separate them.
|
||||
if (text == "fwait" || strings.HasSuffix(text, " fwait")) && dec.nenc >= len(strings.Fields(text)) && dec.enc[len(strings.Fields(text))-1] == 0x9B {
|
||||
return true
|
||||
}
|
||||
|
||||
// 0F 18 77 11:
|
||||
// Invalid instructions for which libopcodes prints "nop/reserved".
|
||||
// Perhaps libopcodes is reporting an older encoding.
|
||||
if text == "BAD" && contains(dec.text, "nop/reserved") {
|
||||
return true
|
||||
}
|
||||
|
||||
// 0F C7 B0 11 22 33 44: libopcodes says vmptrld 0x44332211(%eax); we say rdrand %eax.
|
||||
// TODO(rsc): Fix, since we are probably wrong, but we don't have vmptrld in the manual.
|
||||
if contains(text, "rdrand") && contains(dec.text, "vmptrld", "vmxon", "vmclear") {
|
||||
return true
|
||||
}
|
||||
|
||||
// DD C8: libopcodes says FNOP but the Intel manual is clear FNOP is only D9 D0.
|
||||
// Perhaps libopcodes is reporting an older encoding.
|
||||
if text == "BAD" && contains(dec.text, "fnop") && (dec.enc[0] != 0xD9 || dec.enc[1] != 0xD0) {
|
||||
return true
|
||||
}
|
||||
|
||||
// 66 90: libopcodes says xchg %ax,%ax; we say 'data16 nop'.
|
||||
// The 16-bit swap will preserve the high bits of the register,
|
||||
// so they are the same.
|
||||
if contains(text, "nop") && contains(dec.text, "xchg %ax,%ax") {
|
||||
return true
|
||||
}
|
||||
|
||||
// If there are multiple prefixes, allow libopcodes to use an alternate name.
|
||||
if size == 1 && dec.nenc == 1 && prefixByte[text] > 0 && prefixByte[text] == prefixByte[dec.text] {
|
||||
return true
|
||||
}
|
||||
|
||||
// 26 9B: libopcodes reports "fwait"/1, ignoring segment prefix.
|
||||
// https://sourceware.org/bugzilla/show_bug.cgi?id=16891
|
||||
// F0 82: Decode="lock"/1 but libopcodes="lock (bad)"/2.
|
||||
if size == 1 && dec.nenc >= 1 && prefixByte[text] == dec.enc[0] && contains(dec.text, "(bad)", "fwait", "fnop") {
|
||||
return true
|
||||
}
|
||||
|
||||
// libopcodes interprets 660f801122 as taking a rel16 but
|
||||
// truncating the address at 16 bits. Not sure what is correct.
|
||||
if contains(text, ".+0x2211", ".+0x11") && contains(dec.text, " .-") {
|
||||
return true
|
||||
}
|
||||
|
||||
// 66 F3 0F D6 C5, 66 F2 0F D6 C0: libopcodes reports use of XMM register instead of MMX register,
|
||||
// but only when the instruction has a 66 prefix. Maybe they know something we don't.
|
||||
if countPrefix(inst, 0x66) > 0 && contains(dec.text, "movdq2q", "movq2dq") && !contains(dec.text, "%mm") {
|
||||
return true
|
||||
}
|
||||
|
||||
// 0F 01 F8, 0F 05, 0F 07: these are 64-bit instructions but libopcodes accepts them.
|
||||
if (text == "BAD" || size == 1 && isPrefix(text)) && contains(dec.text, "swapgs", "syscall", "sysret", "rdfsbase", "rdgsbase", "wrfsbase", "wrgsbase") {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Instructions known to libopcodes (or xed) but not to us.
|
||||
// Most of these come from supplementary manuals of one form or another.
|
||||
var unsupported = strings.Fields(`
|
||||
bndc
|
||||
bndl
|
||||
bndm
|
||||
bnds
|
||||
clac
|
||||
clgi
|
||||
femms
|
||||
fldln
|
||||
getsec
|
||||
invlpga
|
||||
kmov
|
||||
montmul
|
||||
pavg
|
||||
pf2i
|
||||
pfacc
|
||||
pfadd
|
||||
pfcmp
|
||||
pfmax
|
||||
pfmin
|
||||
pfmul
|
||||
pfna
|
||||
pfpnac
|
||||
pfrc
|
||||
pfrs
|
||||
pfsub
|
||||
phadd
|
||||
phsub
|
||||
pi2f
|
||||
pmulhr
|
||||
prefetch
|
||||
pswap
|
||||
ptest
|
||||
rdseed
|
||||
sha1
|
||||
sha256
|
||||
skinit
|
||||
stac
|
||||
stgi
|
||||
vadd
|
||||
vand
|
||||
vcmp
|
||||
vcomis
|
||||
vcvt
|
||||
vcvt
|
||||
vdiv
|
||||
vhadd
|
||||
vhsub
|
||||
vld
|
||||
vmax
|
||||
vmcall
|
||||
vmfunc
|
||||
vmin
|
||||
vmlaunch
|
||||
vmload
|
||||
vmmcall
|
||||
vmov
|
||||
vmov
|
||||
vmov
|
||||
vmptrld
|
||||
vmptrst
|
||||
vmread
|
||||
vmresume
|
||||
vmrun
|
||||
vmsave
|
||||
vmul
|
||||
vmwrite
|
||||
vmxoff
|
||||
vor
|
||||
vpack
|
||||
vpadd
|
||||
vpand
|
||||
vpavg
|
||||
vpcmp
|
||||
vpcmp
|
||||
vpins
|
||||
vpmadd
|
||||
vpmax
|
||||
vpmin
|
||||
vpmul
|
||||
vpmul
|
||||
vpor
|
||||
vpsad
|
||||
vpshuf
|
||||
vpsll
|
||||
vpsra
|
||||
vpsrad
|
||||
vpsrl
|
||||
vpsub
|
||||
vpunp
|
||||
vpxor
|
||||
vrcp
|
||||
vrsqrt
|
||||
vshuf
|
||||
vsqrt
|
||||
vsub
|
||||
vucomis
|
||||
vunp
|
||||
vxor
|
||||
vzero
|
||||
xcrypt
|
||||
xsha1
|
||||
xsha256
|
||||
xstore-rng
|
||||
insertq
|
||||
extrq
|
||||
vmclear
|
||||
invvpid
|
||||
adox
|
||||
vmxon
|
||||
invept
|
||||
adcx
|
||||
vmclear
|
||||
prefetchwt1
|
||||
enclu
|
||||
encls
|
||||
salc
|
||||
fstpnce
|
||||
fdisi8087_nop
|
||||
fsetpm287_nop
|
||||
feni8087_nop
|
||||
syscall
|
||||
sysret
|
||||
`)
|
||||
|
||||
// Instructions known to us but not to libopcodes (at least in binutils 2.24).
|
||||
var libopcodesUnsupported = strings.Fields(`
|
||||
addsubps
|
||||
aes
|
||||
blend
|
||||
cvttpd2dq
|
||||
dpp
|
||||
extract
|
||||
haddps
|
||||
hsubps
|
||||
insert
|
||||
invpcid
|
||||
lddqu
|
||||
movmsk
|
||||
movnt
|
||||
movq2dq
|
||||
mps
|
||||
pack
|
||||
pblend
|
||||
pclmul
|
||||
pcmp
|
||||
pext
|
||||
phmin
|
||||
pins
|
||||
pmax
|
||||
pmin
|
||||
pmov
|
||||
pmovmsk
|
||||
pmul
|
||||
popcnt
|
||||
pslld
|
||||
psllq
|
||||
psllw
|
||||
psrad
|
||||
psraw
|
||||
psrl
|
||||
ptest
|
||||
punpck
|
||||
round
|
||||
xrstor
|
||||
xsavec
|
||||
xsaves
|
||||
comis
|
||||
ucomis
|
||||
movhps
|
||||
movntps
|
||||
rsqrt
|
||||
rcpp
|
||||
puncpck
|
||||
bsf
|
||||
movq2dq
|
||||
cvttpd2dq
|
||||
movq
|
||||
hsubpd
|
||||
movdqa
|
||||
movhpd
|
||||
addsubpd
|
||||
movd
|
||||
haddpd
|
||||
cvtps2dq
|
||||
bsr
|
||||
cvtdq2ps
|
||||
rdrand
|
||||
maskmov
|
||||
movq2dq
|
||||
movlhps
|
||||
movbe
|
||||
movlpd
|
||||
`)
|
|
@ -0,0 +1,313 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package x86asm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"debug/elf"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Apologies for the proprietary path, but we need objdump 2.24 + some committed patches that will land in 2.25.
|
||||
const objdumpPath = "/Users/rsc/bin/objdump2"
|
||||
|
||||
func testObjdump32(t *testing.T, generate func(func([]byte))) {
|
||||
testObjdumpArch(t, generate, 32)
|
||||
}
|
||||
|
||||
func testObjdump64(t *testing.T, generate func(func([]byte))) {
|
||||
testObjdumpArch(t, generate, 64)
|
||||
}
|
||||
|
||||
func testObjdumpArch(t *testing.T, generate func(func([]byte)), arch int) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping objdump test in short mode")
|
||||
}
|
||||
if _, err := os.Stat(objdumpPath); err != nil {
|
||||
t.Skip(err)
|
||||
}
|
||||
|
||||
testExtDis(t, "gnu", arch, objdump, generate, allowedMismatchObjdump)
|
||||
}
|
||||
|
||||
func objdump(ext *ExtDis) error {
|
||||
// File already written with instructions; add ELF header.
|
||||
if ext.Arch == 32 {
|
||||
if err := writeELF32(ext.File, ext.Size); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := writeELF64(ext.File, ext.Size); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
b, err := ext.Run(objdumpPath, "-d", "-z", ext.File.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
nmatch int
|
||||
reading bool
|
||||
next uint32 = start
|
||||
addr uint32
|
||||
encbuf [32]byte
|
||||
enc []byte
|
||||
text string
|
||||
)
|
||||
flush := func() {
|
||||
if addr == next {
|
||||
switch text {
|
||||
case "repz":
|
||||
text = "rep"
|
||||
case "repnz":
|
||||
text = "repn"
|
||||
default:
|
||||
text = strings.Replace(text, "repz ", "rep ", -1)
|
||||
text = strings.Replace(text, "repnz ", "repn ", -1)
|
||||
}
|
||||
if m := pcrelw.FindStringSubmatch(text); m != nil {
|
||||
targ, _ := strconv.ParseUint(m[2], 16, 64)
|
||||
text = fmt.Sprintf("%s .%+#x", m[1], int16(uint32(targ)-uint32(uint16(addr))-uint32(len(enc))))
|
||||
}
|
||||
if m := pcrel.FindStringSubmatch(text); m != nil {
|
||||
targ, _ := strconv.ParseUint(m[2], 16, 64)
|
||||
text = fmt.Sprintf("%s .%+#x", m[1], int32(uint32(targ)-addr-uint32(len(enc))))
|
||||
}
|
||||
text = strings.Replace(text, "0x0(", "(", -1)
|
||||
text = strings.Replace(text, "%st(0)", "%st", -1)
|
||||
|
||||
ext.Dec <- ExtInst{addr, encbuf, len(enc), text}
|
||||
encbuf = [32]byte{}
|
||||
enc = nil
|
||||
next += 32
|
||||
}
|
||||
}
|
||||
var textangle = []byte("<.text>:")
|
||||
for {
|
||||
line, err := b.ReadSlice('\n')
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return fmt.Errorf("reading objdump output: %v", err)
|
||||
}
|
||||
if bytes.Contains(line, textangle) {
|
||||
reading = true
|
||||
continue
|
||||
}
|
||||
if !reading {
|
||||
continue
|
||||
}
|
||||
if debug {
|
||||
os.Stdout.Write(line)
|
||||
}
|
||||
if enc1 := parseContinuation(line, encbuf[:len(enc)]); enc1 != nil {
|
||||
enc = enc1
|
||||
continue
|
||||
}
|
||||
flush()
|
||||
nmatch++
|
||||
addr, enc, text = parseLine(line, encbuf[:0])
|
||||
if addr > next {
|
||||
return fmt.Errorf("address out of sync expected <= %#x at %q in:\n%s", next, line, line)
|
||||
}
|
||||
}
|
||||
flush()
|
||||
if next != start+uint32(ext.Size) {
|
||||
return fmt.Errorf("not enough results found [%d %d]", next, start+ext.Size)
|
||||
}
|
||||
if err := ext.Wait(); err != nil {
|
||||
return fmt.Errorf("exec: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseLine(line []byte, encstart []byte) (addr uint32, enc []byte, text string) {
|
||||
oline := line
|
||||
i := index(line, ":\t")
|
||||
if i < 0 {
|
||||
log.Fatalf("cannot parse disassembly: %q", oline)
|
||||
}
|
||||
x, err := strconv.ParseUint(string(trimSpace(line[:i])), 16, 32)
|
||||
if err != nil {
|
||||
log.Fatalf("cannot parse disassembly: %q", oline)
|
||||
}
|
||||
addr = uint32(x)
|
||||
line = line[i+2:]
|
||||
i = bytes.IndexByte(line, '\t')
|
||||
if i < 0 {
|
||||
log.Fatalf("cannot parse disassembly: %q", oline)
|
||||
}
|
||||
enc, ok := parseHex(line[:i], encstart)
|
||||
if !ok {
|
||||
log.Fatalf("cannot parse disassembly: %q", oline)
|
||||
}
|
||||
line = trimSpace(line[i:])
|
||||
if i := bytes.IndexByte(line, '#'); i >= 0 {
|
||||
line = trimSpace(line[:i])
|
||||
}
|
||||
text = string(fixSpace(line))
|
||||
return
|
||||
}
|
||||
|
||||
func parseContinuation(line []byte, enc []byte) []byte {
|
||||
i := index(line, ":\t")
|
||||
if i < 0 {
|
||||
return nil
|
||||
}
|
||||
line = line[i+1:]
|
||||
enc, _ = parseHex(line, enc)
|
||||
return enc
|
||||
}
|
||||
|
||||
// writeELF32 writes an ELF32 header to the file,
|
||||
// describing a text segment that starts at start
|
||||
// and extends for size bytes.
|
||||
func writeELF32(f *os.File, size int) error {
|
||||
f.Seek(0, io.SeekStart)
|
||||
var hdr elf.Header32
|
||||
var prog elf.Prog32
|
||||
var sect elf.Section32
|
||||
var buf bytes.Buffer
|
||||
binary.Write(&buf, binary.LittleEndian, &hdr)
|
||||
off1 := buf.Len()
|
||||
binary.Write(&buf, binary.LittleEndian, &prog)
|
||||
off2 := buf.Len()
|
||||
binary.Write(&buf, binary.LittleEndian, §)
|
||||
off3 := buf.Len()
|
||||
buf.Reset()
|
||||
data := byte(elf.ELFDATA2LSB)
|
||||
hdr = elf.Header32{
|
||||
Ident: [16]byte{0x7F, 'E', 'L', 'F', 1, data, 1},
|
||||
Type: 2,
|
||||
Machine: uint16(elf.EM_386),
|
||||
Version: 1,
|
||||
Entry: start,
|
||||
Phoff: uint32(off1),
|
||||
Shoff: uint32(off2),
|
||||
Flags: 0x05000002,
|
||||
Ehsize: uint16(off1),
|
||||
Phentsize: uint16(off2 - off1),
|
||||
Phnum: 1,
|
||||
Shentsize: uint16(off3 - off2),
|
||||
Shnum: 3,
|
||||
Shstrndx: 2,
|
||||
}
|
||||
binary.Write(&buf, binary.LittleEndian, &hdr)
|
||||
prog = elf.Prog32{
|
||||
Type: 1,
|
||||
Off: start,
|
||||
Vaddr: start,
|
||||
Paddr: start,
|
||||
Filesz: uint32(size),
|
||||
Memsz: uint32(size),
|
||||
Flags: 5,
|
||||
Align: start,
|
||||
}
|
||||
binary.Write(&buf, binary.LittleEndian, &prog)
|
||||
binary.Write(&buf, binary.LittleEndian, §) // NULL section
|
||||
sect = elf.Section32{
|
||||
Name: 1,
|
||||
Type: uint32(elf.SHT_PROGBITS),
|
||||
Addr: start,
|
||||
Off: start,
|
||||
Size: uint32(size),
|
||||
Flags: uint32(elf.SHF_ALLOC | elf.SHF_EXECINSTR),
|
||||
Addralign: 4,
|
||||
}
|
||||
binary.Write(&buf, binary.LittleEndian, §) // .text
|
||||
sect = elf.Section32{
|
||||
Name: uint32(len("\x00.text\x00")),
|
||||
Type: uint32(elf.SHT_STRTAB),
|
||||
Addr: 0,
|
||||
Off: uint32(off2 + (off3-off2)*3),
|
||||
Size: uint32(len("\x00.text\x00.shstrtab\x00")),
|
||||
Addralign: 1,
|
||||
}
|
||||
binary.Write(&buf, binary.LittleEndian, §)
|
||||
buf.WriteString("\x00.text\x00.shstrtab\x00")
|
||||
f.Write(buf.Bytes())
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeELF64 writes an ELF64 header to the file,
|
||||
// describing a text segment that starts at start
|
||||
// and extends for size bytes.
|
||||
func writeELF64(f *os.File, size int) error {
|
||||
f.Seek(0, io.SeekStart)
|
||||
var hdr elf.Header64
|
||||
var prog elf.Prog64
|
||||
var sect elf.Section64
|
||||
var buf bytes.Buffer
|
||||
binary.Write(&buf, binary.LittleEndian, &hdr)
|
||||
off1 := buf.Len()
|
||||
binary.Write(&buf, binary.LittleEndian, &prog)
|
||||
off2 := buf.Len()
|
||||
binary.Write(&buf, binary.LittleEndian, §)
|
||||
off3 := buf.Len()
|
||||
buf.Reset()
|
||||
data := byte(elf.ELFDATA2LSB)
|
||||
hdr = elf.Header64{
|
||||
Ident: [16]byte{0x7F, 'E', 'L', 'F', 2, data, 1},
|
||||
Type: 2,
|
||||
Machine: uint16(elf.EM_X86_64),
|
||||
Version: 1,
|
||||
Entry: start,
|
||||
Phoff: uint64(off1),
|
||||
Shoff: uint64(off2),
|
||||
Flags: 0x05000002,
|
||||
Ehsize: uint16(off1),
|
||||
Phentsize: uint16(off2 - off1),
|
||||
Phnum: 1,
|
||||
Shentsize: uint16(off3 - off2),
|
||||
Shnum: 3,
|
||||
Shstrndx: 2,
|
||||
}
|
||||
binary.Write(&buf, binary.LittleEndian, &hdr)
|
||||
prog = elf.Prog64{
|
||||
Type: 1,
|
||||
Off: start,
|
||||
Vaddr: start,
|
||||
Paddr: start,
|
||||
Filesz: uint64(size),
|
||||
Memsz: uint64(size),
|
||||
Flags: 5,
|
||||
Align: start,
|
||||
}
|
||||
binary.Write(&buf, binary.LittleEndian, &prog)
|
||||
binary.Write(&buf, binary.LittleEndian, §) // NULL section
|
||||
sect = elf.Section64{
|
||||
Name: 1,
|
||||
Type: uint32(elf.SHT_PROGBITS),
|
||||
Addr: start,
|
||||
Off: start,
|
||||
Size: uint64(size),
|
||||
Flags: uint64(elf.SHF_ALLOC | elf.SHF_EXECINSTR),
|
||||
Addralign: 4,
|
||||
}
|
||||
binary.Write(&buf, binary.LittleEndian, §) // .text
|
||||
sect = elf.Section64{
|
||||
Name: uint32(len("\x00.text\x00")),
|
||||
Type: uint32(elf.SHT_STRTAB),
|
||||
Addr: 0,
|
||||
Off: uint64(off2 + (off3-off2)*3),
|
||||
Size: uint64(len("\x00.text\x00.shstrtab\x00")),
|
||||
Addralign: 1,
|
||||
}
|
||||
binary.Write(&buf, binary.LittleEndian, §)
|
||||
buf.WriteString("\x00.text\x00.shstrtab\x00")
|
||||
f.Write(buf.Bytes())
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package x86asm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const plan9Path = "testdata/libmach8db"
|
||||
|
||||
func testPlan9Arch(t *testing.T, arch int, generate func(func([]byte))) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping libmach test in short mode")
|
||||
}
|
||||
if _, err := os.Stat(plan9Path); err != nil {
|
||||
t.Skip(err)
|
||||
}
|
||||
|
||||
testExtDis(t, "plan9", arch, plan9, generate, allowedMismatchPlan9)
|
||||
}
|
||||
|
||||
func testPlan932(t *testing.T, generate func(func([]byte))) {
|
||||
testPlan9Arch(t, 32, generate)
|
||||
}
|
||||
|
||||
func testPlan964(t *testing.T, generate func(func([]byte))) {
|
||||
testPlan9Arch(t, 64, generate)
|
||||
}
|
||||
|
||||
func plan9(ext *ExtDis) error {
|
||||
flag := "-8"
|
||||
if ext.Arch == 64 {
|
||||
flag = "-6"
|
||||
}
|
||||
b, err := ext.Run(plan9Path, flag, ext.File.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nmatch := 0
|
||||
next := uint32(start)
|
||||
var (
|
||||
addr uint32
|
||||
encbuf [32]byte
|
||||
enc []byte
|
||||
text string
|
||||
)
|
||||
|
||||
for {
|
||||
line, err := b.ReadSlice('\n')
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return fmt.Errorf("reading libmach8db output: %v", err)
|
||||
}
|
||||
if debug {
|
||||
os.Stdout.Write(line)
|
||||
}
|
||||
nmatch++
|
||||
addr, enc, text = parseLinePlan9(line, encbuf[:0])
|
||||
if addr > next {
|
||||
return fmt.Errorf("address out of sync expected <= %#x at %q in:\n%s", next, line, line)
|
||||
}
|
||||
if addr < next {
|
||||
continue
|
||||
}
|
||||
if m := pcrelw.FindStringSubmatch(text); m != nil {
|
||||
targ, _ := strconv.ParseUint(m[2], 16, 64)
|
||||
text = fmt.Sprintf("%s .%+#x", m[1], int16(uint32(targ)-uint32(uint16(addr))-uint32(len(enc))))
|
||||
}
|
||||
if m := pcrel.FindStringSubmatch(text); m != nil {
|
||||
targ, _ := strconv.ParseUint(m[2], 16, 64)
|
||||
text = fmt.Sprintf("%s .%+#x", m[1], int32(uint32(targ)-addr-uint32(len(enc))))
|
||||
}
|
||||
ext.Dec <- ExtInst{addr, encbuf, len(enc), text}
|
||||
encbuf = [32]byte{}
|
||||
enc = nil
|
||||
next += 32
|
||||
}
|
||||
if next != start+uint32(ext.Size) {
|
||||
return fmt.Errorf("not enough results found [%d %d]", next, start+ext.Size)
|
||||
}
|
||||
if err := ext.Wait(); err != nil {
|
||||
return fmt.Errorf("exec: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseLinePlan9(line []byte, encstart []byte) (addr uint32, enc []byte, text string) {
|
||||
i := bytes.IndexByte(line, ' ')
|
||||
if i < 0 || line[0] != '0' || line[1] != 'x' {
|
||||
log.Fatalf("cannot parse disassembly: %q", line)
|
||||
}
|
||||
j := bytes.IndexByte(line[i+1:], ' ')
|
||||
if j < 0 {
|
||||
log.Fatalf("cannot parse disassembly: %q", line)
|
||||
}
|
||||
j += i + 1
|
||||
x, err := strconv.ParseUint(string(trimSpace(line[2:i])), 16, 32)
|
||||
if err != nil {
|
||||
log.Fatalf("cannot parse disassembly: %q", line)
|
||||
}
|
||||
addr = uint32(x)
|
||||
enc, ok := parseHex(line[i+1:j], encstart)
|
||||
if !ok {
|
||||
log.Fatalf("cannot parse disassembly: %q", line)
|
||||
}
|
||||
return addr, enc, string(fixSpace(line[j+1:]))
|
||||
}
|
|
@ -0,0 +1,381 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package x86asm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type SymLookup func(uint64) (string, uint64)
|
||||
|
||||
// GoSyntax returns the Go assembler syntax for the instruction.
|
||||
// The syntax was originally defined by Plan 9.
|
||||
// The pc is the program counter of the instruction, used for expanding
|
||||
// PC-relative addresses into absolute ones.
|
||||
// The symname function queries the symbol table for the program
|
||||
// being disassembled. Given a target address it returns the name and base
|
||||
// address of the symbol containing the target, if any; otherwise it returns "", 0.
|
||||
func GoSyntax(inst Inst, pc uint64, symname SymLookup) string {
|
||||
if symname == nil {
|
||||
symname = func(uint64) (string, uint64) { return "", 0 }
|
||||
}
|
||||
var args []string
|
||||
for i := len(inst.Args) - 1; i >= 0; i-- {
|
||||
a := inst.Args[i]
|
||||
if a == nil {
|
||||
continue
|
||||
}
|
||||
args = append(args, plan9Arg(&inst, pc, symname, a))
|
||||
}
|
||||
|
||||
var rep string
|
||||
var last Prefix
|
||||
for _, p := range inst.Prefix {
|
||||
if p == 0 || p.IsREX() || p.IsVEX() {
|
||||
break
|
||||
}
|
||||
|
||||
switch {
|
||||
// Don't show prefixes implied by the instruction text.
|
||||
case p&0xFF00 == PrefixImplicit:
|
||||
continue
|
||||
// Only REP and REPN are recognized repeaters. Plan 9 syntax
|
||||
// treats them as separate opcodes.
|
||||
case p&0xFF == PrefixREP:
|
||||
rep = "REP; "
|
||||
case p&0xFF == PrefixREPN:
|
||||
rep = "REPNE; "
|
||||
default:
|
||||
last = p
|
||||
}
|
||||
}
|
||||
|
||||
prefix := ""
|
||||
switch last & 0xFF {
|
||||
case 0, 0x66, 0x67:
|
||||
// ignore
|
||||
default:
|
||||
prefix += last.String() + " "
|
||||
}
|
||||
|
||||
op := inst.Op.String()
|
||||
if plan9Suffix[inst.Op] {
|
||||
s := inst.DataSize
|
||||
if inst.MemBytes != 0 {
|
||||
s = inst.MemBytes * 8
|
||||
}
|
||||
switch s {
|
||||
case 8:
|
||||
op += "B"
|
||||
case 16:
|
||||
op += "W"
|
||||
case 32:
|
||||
op += "L"
|
||||
case 64:
|
||||
op += "Q"
|
||||
}
|
||||
}
|
||||
|
||||
if args != nil {
|
||||
op += " " + strings.Join(args, ", ")
|
||||
}
|
||||
|
||||
return rep + prefix + op
|
||||
}
|
||||
|
||||
func plan9Arg(inst *Inst, pc uint64, symname func(uint64) (string, uint64), arg Arg) string {
|
||||
switch a := arg.(type) {
|
||||
case Reg:
|
||||
return plan9Reg[a]
|
||||
case Rel:
|
||||
if pc == 0 {
|
||||
break
|
||||
}
|
||||
// If the absolute address is the start of a symbol, use the name.
|
||||
// Otherwise use the raw address, so that things like relative
|
||||
// jumps show up as JMP 0x123 instead of JMP f+10(SB).
|
||||
// It is usually easier to search for 0x123 than to do the mental
|
||||
// arithmetic to find f+10.
|
||||
addr := pc + uint64(inst.Len) + uint64(a)
|
||||
if s, base := symname(addr); s != "" && addr == base {
|
||||
return fmt.Sprintf("%s(SB)", s)
|
||||
}
|
||||
return fmt.Sprintf("%#x", addr)
|
||||
|
||||
case Imm:
|
||||
if s, base := symname(uint64(a)); s != "" {
|
||||
suffix := ""
|
||||
if uint64(a) != base {
|
||||
suffix = fmt.Sprintf("%+d", uint64(a)-base)
|
||||
}
|
||||
return fmt.Sprintf("$%s%s(SB)", s, suffix)
|
||||
}
|
||||
if inst.Mode == 32 {
|
||||
return fmt.Sprintf("$%#x", uint32(a))
|
||||
}
|
||||
if Imm(int32(a)) == a {
|
||||
return fmt.Sprintf("$%#x", int64(a))
|
||||
}
|
||||
return fmt.Sprintf("$%#x", uint64(a))
|
||||
case Mem:
|
||||
if s, disp := memArgToSymbol(a, pc, inst.Len, symname); s != "" {
|
||||
suffix := ""
|
||||
if disp != 0 {
|
||||
suffix = fmt.Sprintf("%+d", disp)
|
||||
}
|
||||
return fmt.Sprintf("%s%s(SB)", s, suffix)
|
||||
}
|
||||
s := ""
|
||||
if a.Segment != 0 {
|
||||
s += fmt.Sprintf("%s:", plan9Reg[a.Segment])
|
||||
}
|
||||
if a.Disp != 0 {
|
||||
s += fmt.Sprintf("%#x", a.Disp)
|
||||
} else {
|
||||
s += "0"
|
||||
}
|
||||
if a.Base != 0 {
|
||||
s += fmt.Sprintf("(%s)", plan9Reg[a.Base])
|
||||
}
|
||||
if a.Index != 0 && a.Scale != 0 {
|
||||
s += fmt.Sprintf("(%s*%d)", plan9Reg[a.Index], a.Scale)
|
||||
}
|
||||
return s
|
||||
}
|
||||
return arg.String()
|
||||
}
|
||||
|
||||
func memArgToSymbol(a Mem, pc uint64, instrLen int, symname SymLookup) (string, int64) {
|
||||
if a.Segment != 0 || a.Disp == 0 || a.Index != 0 || a.Scale != 0 {
|
||||
return "", 0
|
||||
}
|
||||
|
||||
var disp uint64
|
||||
switch a.Base {
|
||||
case IP, EIP, RIP:
|
||||
disp = uint64(a.Disp + int64(pc) + int64(instrLen))
|
||||
case 0:
|
||||
disp = uint64(a.Disp)
|
||||
default:
|
||||
return "", 0
|
||||
}
|
||||
|
||||
s, base := symname(disp)
|
||||
return s, int64(disp) - int64(base)
|
||||
}
|
||||
|
||||
var plan9Suffix = [maxOp + 1]bool{
|
||||
ADC: true,
|
||||
ADD: true,
|
||||
AND: true,
|
||||
BSF: true,
|
||||
BSR: true,
|
||||
BT: true,
|
||||
BTC: true,
|
||||
BTR: true,
|
||||
BTS: true,
|
||||
CMP: true,
|
||||
CMPXCHG: true,
|
||||
CVTSI2SD: true,
|
||||
CVTSI2SS: true,
|
||||
CVTSD2SI: true,
|
||||
CVTSS2SI: true,
|
||||
CVTTSD2SI: true,
|
||||
CVTTSS2SI: true,
|
||||
DEC: true,
|
||||
DIV: true,
|
||||
FLDENV: true,
|
||||
FRSTOR: true,
|
||||
IDIV: true,
|
||||
IMUL: true,
|
||||
IN: true,
|
||||
INC: true,
|
||||
LEA: true,
|
||||
MOV: true,
|
||||
MOVNTI: true,
|
||||
MUL: true,
|
||||
NEG: true,
|
||||
NOP: true,
|
||||
NOT: true,
|
||||
OR: true,
|
||||
OUT: true,
|
||||
POP: true,
|
||||
POPA: true,
|
||||
PUSH: true,
|
||||
PUSHA: true,
|
||||
RCL: true,
|
||||
RCR: true,
|
||||
ROL: true,
|
||||
ROR: true,
|
||||
SAR: true,
|
||||
SBB: true,
|
||||
SHL: true,
|
||||
SHLD: true,
|
||||
SHR: true,
|
||||
SHRD: true,
|
||||
SUB: true,
|
||||
TEST: true,
|
||||
XADD: true,
|
||||
XCHG: true,
|
||||
XOR: true,
|
||||
}
|
||||
|
||||
var plan9Reg = [...]string{
|
||||
AL: "AL",
|
||||
CL: "CL",
|
||||
BL: "BL",
|
||||
DL: "DL",
|
||||
AH: "AH",
|
||||
CH: "CH",
|
||||
BH: "BH",
|
||||
DH: "DH",
|
||||
SPB: "SP",
|
||||
BPB: "BP",
|
||||
SIB: "SI",
|
||||
DIB: "DI",
|
||||
R8B: "R8",
|
||||
R9B: "R9",
|
||||
R10B: "R10",
|
||||
R11B: "R11",
|
||||
R12B: "R12",
|
||||
R13B: "R13",
|
||||
R14B: "R14",
|
||||
R15B: "R15",
|
||||
AX: "AX",
|
||||
CX: "CX",
|
||||
BX: "BX",
|
||||
DX: "DX",
|
||||
SP: "SP",
|
||||
BP: "BP",
|
||||
SI: "SI",
|
||||
DI: "DI",
|
||||
R8W: "R8",
|
||||
R9W: "R9",
|
||||
R10W: "R10",
|
||||
R11W: "R11",
|
||||
R12W: "R12",
|
||||
R13W: "R13",
|
||||
R14W: "R14",
|
||||
R15W: "R15",
|
||||
EAX: "AX",
|
||||
ECX: "CX",
|
||||
EDX: "DX",
|
||||
EBX: "BX",
|
||||
ESP: "SP",
|
||||
EBP: "BP",
|
||||
ESI: "SI",
|
||||
EDI: "DI",
|
||||
R8L: "R8",
|
||||
R9L: "R9",
|
||||
R10L: "R10",
|
||||
R11L: "R11",
|
||||
R12L: "R12",
|
||||
R13L: "R13",
|
||||
R14L: "R14",
|
||||
R15L: "R15",
|
||||
RAX: "AX",
|
||||
RCX: "CX",
|
||||
RDX: "DX",
|
||||
RBX: "BX",
|
||||
RSP: "SP",
|
||||
RBP: "BP",
|
||||
RSI: "SI",
|
||||
RDI: "DI",
|
||||
R8: "R8",
|
||||
R9: "R9",
|
||||
R10: "R10",
|
||||
R11: "R11",
|
||||
R12: "R12",
|
||||
R13: "R13",
|
||||
R14: "R14",
|
||||
R15: "R15",
|
||||
IP: "IP",
|
||||
EIP: "IP",
|
||||
RIP: "IP",
|
||||
F0: "F0",
|
||||
F1: "F1",
|
||||
F2: "F2",
|
||||
F3: "F3",
|
||||
F4: "F4",
|
||||
F5: "F5",
|
||||
F6: "F6",
|
||||
F7: "F7",
|
||||
M0: "M0",
|
||||
M1: "M1",
|
||||
M2: "M2",
|
||||
M3: "M3",
|
||||
M4: "M4",
|
||||
M5: "M5",
|
||||
M6: "M6",
|
||||
M7: "M7",
|
||||
X0: "X0",
|
||||
X1: "X1",
|
||||
X2: "X2",
|
||||
X3: "X3",
|
||||
X4: "X4",
|
||||
X5: "X5",
|
||||
X6: "X6",
|
||||
X7: "X7",
|
||||
X8: "X8",
|
||||
X9: "X9",
|
||||
X10: "X10",
|
||||
X11: "X11",
|
||||
X12: "X12",
|
||||
X13: "X13",
|
||||
X14: "X14",
|
||||
X15: "X15",
|
||||
CS: "CS",
|
||||
SS: "SS",
|
||||
DS: "DS",
|
||||
ES: "ES",
|
||||
FS: "FS",
|
||||
GS: "GS",
|
||||
GDTR: "GDTR",
|
||||
IDTR: "IDTR",
|
||||
LDTR: "LDTR",
|
||||
MSW: "MSW",
|
||||
TASK: "TASK",
|
||||
CR0: "CR0",
|
||||
CR1: "CR1",
|
||||
CR2: "CR2",
|
||||
CR3: "CR3",
|
||||
CR4: "CR4",
|
||||
CR5: "CR5",
|
||||
CR6: "CR6",
|
||||
CR7: "CR7",
|
||||
CR8: "CR8",
|
||||
CR9: "CR9",
|
||||
CR10: "CR10",
|
||||
CR11: "CR11",
|
||||
CR12: "CR12",
|
||||
CR13: "CR13",
|
||||
CR14: "CR14",
|
||||
CR15: "CR15",
|
||||
DR0: "DR0",
|
||||
DR1: "DR1",
|
||||
DR2: "DR2",
|
||||
DR3: "DR3",
|
||||
DR4: "DR4",
|
||||
DR5: "DR5",
|
||||
DR6: "DR6",
|
||||
DR7: "DR7",
|
||||
DR8: "DR8",
|
||||
DR9: "DR9",
|
||||
DR10: "DR10",
|
||||
DR11: "DR11",
|
||||
DR12: "DR12",
|
||||
DR13: "DR13",
|
||||
DR14: "DR14",
|
||||
DR15: "DR15",
|
||||
TR0: "TR0",
|
||||
TR1: "TR1",
|
||||
TR2: "TR2",
|
||||
TR3: "TR3",
|
||||
TR4: "TR4",
|
||||
TR5: "TR5",
|
||||
TR6: "TR6",
|
||||
TR7: "TR7",
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package x86asm
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPlan932Manual(t *testing.T) { testPlan932(t, hexCases(t, plan9ManualTests)) }
|
||||
func TestPlan932Testdata(t *testing.T) { testPlan932(t, concat(basicPrefixes, testdataCases(t))) }
|
||||
func TestPlan932ModRM(t *testing.T) { testPlan932(t, concat(basicPrefixes, enumModRM)) }
|
||||
func TestPlan932OneByte(t *testing.T) { testBasic(t, testPlan932) }
|
||||
func TestPlan9320F(t *testing.T) { testBasic(t, testPlan932, 0x0F) }
|
||||
func TestPlan9320F38(t *testing.T) { testBasic(t, testPlan932, 0x0F, 0x38) }
|
||||
func TestPlan9320F3A(t *testing.T) { testBasic(t, testPlan932, 0x0F, 0x3A) }
|
||||
func TestPlan932Prefix(t *testing.T) { testPrefix(t, testPlan932) }
|
||||
|
||||
func TestPlan964Manual(t *testing.T) { testPlan964(t, hexCases(t, plan9ManualTests)) }
|
||||
func TestPlan964Testdata(t *testing.T) { testPlan964(t, concat(basicPrefixes, testdataCases(t))) }
|
||||
func TestPlan964ModRM(t *testing.T) { testPlan964(t, concat(basicPrefixes, enumModRM)) }
|
||||
func TestPlan964OneByte(t *testing.T) { testBasic(t, testPlan964) }
|
||||
func TestPlan9640F(t *testing.T) { testBasic(t, testPlan964, 0x0F) }
|
||||
func TestPlan9640F38(t *testing.T) { testBasic(t, testPlan964, 0x0F, 0x38) }
|
||||
func TestPlan9640F3A(t *testing.T) { testBasic(t, testPlan964, 0x0F, 0x3A) }
|
||||
func TestPlan964Prefix(t *testing.T) { testPrefix(t, testPlan964) }
|
||||
|
||||
func TestPlan964REXTestdata(t *testing.T) {
|
||||
testPlan964(t, filter(concat3(basicPrefixes, rexPrefixes, testdataCases(t)), isValidREX))
|
||||
}
|
||||
func TestPlan964REXModRM(t *testing.T) { testPlan964(t, concat3(basicPrefixes, rexPrefixes, enumModRM)) }
|
||||
func TestPlan964REXOneByte(t *testing.T) { testBasicREX(t, testPlan964) }
|
||||
func TestPlan964REX0F(t *testing.T) { testBasicREX(t, testPlan964, 0x0F) }
|
||||
func TestPlan964REX0F38(t *testing.T) { testBasicREX(t, testPlan964, 0x0F, 0x38) }
|
||||
func TestPlan964REX0F3A(t *testing.T) { testBasicREX(t, testPlan964, 0x0F, 0x3A) }
|
||||
func TestPlan964REXPrefix(t *testing.T) { testPrefixREX(t, testPlan964) }
|
||||
|
||||
// plan9ManualTests holds test cases that will be run by TestPlan9Manual32 and TestPlan9Manual64.
|
||||
// If you are debugging a few cases that turned up in a longer run, it can be useful
|
||||
// to list them here and then use -run=Plan9Manual, particularly with tracing enabled.
|
||||
var plan9ManualTests = `
|
||||
`
|
||||
|
||||
// allowedMismatchPlan9 reports whether the mismatch between text and dec
|
||||
// should be allowed by the test.
|
||||
func allowedMismatchPlan9(text string, size int, inst *Inst, dec ExtInst) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Instructions known to us but not to plan9.
|
||||
var plan9Unsupported = strings.Fields(`
|
||||
`)
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,12 @@
|
|||
libmach8db: libmach8db.c
|
||||
9c libmach8db.c && 9l -o libmach8db libmach8db.o; rm libmach8db.o
|
||||
|
||||
newdecode.txt:
|
||||
cd ..; go test -cover -run 'Objdump.*32' -v -timeout 10h -printtests 2>&1 | tee log
|
||||
cd ..; go test -cover -run 'Objdump.*64' -v -timeout 10h -printtests 2>&1 | tee -a log
|
||||
cd ..; go test -cover -run 'Xed.*32' -v -timeout 10h -printtests 2>&1 | tee -a log
|
||||
cd ..; go test -cover -run 'Xed.*64' -v -timeout 10h -printtests 2>&1 | tee -a log
|
||||
cd ..; go test -cover -run 'Plan9.*32' -v -timeout 10h -printtests 2>&1 | tee -a log
|
||||
cd ..; go test -cover -run 'Plan9.*64' -v -timeout 10h -printtests 2>&1 | tee -a log
|
||||
egrep ' (gnu|intel|plan9) ' ../log |sort >newdecode.txt
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,211 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package x86asm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestXed32Manual(t *testing.T) { testXed32(t, hexCases(t, xedManualTests)) }
|
||||
func TestXed32Testdata(t *testing.T) { testXed32(t, concat(basicPrefixes, testdataCases(t))) }
|
||||
func TestXed32ModRM(t *testing.T) { testXed32(t, concat(basicPrefixes, enumModRM)) }
|
||||
func TestXed32OneByte(t *testing.T) { testBasic(t, testXed32) }
|
||||
func TestXed320F(t *testing.T) { testBasic(t, testXed32, 0x0F) }
|
||||
func TestXed320F38(t *testing.T) { testBasic(t, testXed32, 0x0F, 0x38) }
|
||||
func TestXed320F3A(t *testing.T) { testBasic(t, testXed32, 0x0F, 0x3A) }
|
||||
func TestXed32Prefix(t *testing.T) { testPrefix(t, testXed32) }
|
||||
|
||||
func TestXed64Manual(t *testing.T) { testXed64(t, hexCases(t, xedManualTests)) }
|
||||
func TestXed64Testdata(t *testing.T) { testXed64(t, concat(basicPrefixes, testdataCases(t))) }
|
||||
func TestXed64ModRM(t *testing.T) { testXed64(t, concat(basicPrefixes, enumModRM)) }
|
||||
func TestXed64OneByte(t *testing.T) { testBasic(t, testXed64) }
|
||||
func TestXed640F(t *testing.T) { testBasic(t, testXed64, 0x0F) }
|
||||
func TestXed640F38(t *testing.T) { testBasic(t, testXed64, 0x0F, 0x38) }
|
||||
func TestXed640F3A(t *testing.T) { testBasic(t, testXed64, 0x0F, 0x3A) }
|
||||
func TestXed64Prefix(t *testing.T) { testPrefix(t, testXed64) }
|
||||
|
||||
func TestXed64REXTestdata(t *testing.T) {
|
||||
testXed64(t, filter(concat3(basicPrefixes, rexPrefixes, testdataCases(t)), isValidREX))
|
||||
}
|
||||
func TestXed64REXModRM(t *testing.T) { testXed64(t, concat3(basicPrefixes, rexPrefixes, enumModRM)) }
|
||||
func TestXed64REXOneByte(t *testing.T) { testBasicREX(t, testXed64) }
|
||||
func TestXed64REX0F(t *testing.T) { testBasicREX(t, testXed64, 0x0F) }
|
||||
func TestXed64REX0F38(t *testing.T) { testBasicREX(t, testXed64, 0x0F, 0x38) }
|
||||
func TestXed64REX0F3A(t *testing.T) { testBasicREX(t, testXed64, 0x0F, 0x3A) }
|
||||
func TestXed64REXPrefix(t *testing.T) { testPrefixREX(t, testXed64) }
|
||||
|
||||
// xedManualTests holds test cases that will be run by TestXedManual32 and TestXedManual64.
|
||||
// If you are debugging a few cases that turned up in a longer run, it can be useful
|
||||
// to list them here and then use -run=XedManual, particularly with tracing enabled.
|
||||
var xedManualTests = `
|
||||
6690
|
||||
`
|
||||
|
||||
// allowedMismatchXed reports whether the mismatch between text and dec
|
||||
// should be allowed by the test.
|
||||
func allowedMismatchXed(text string, size int, inst *Inst, dec ExtInst) bool {
|
||||
if (contains(text, "error:") || isPrefix(text) && size == 1) && contains(dec.text, "GENERAL_ERROR", "INSTR_TOO_LONG", "BAD_LOCK_PREFIX") {
|
||||
return true
|
||||
}
|
||||
|
||||
if contains(dec.text, "BAD_LOCK_PREFIX") && countExactPrefix(inst, PrefixLOCK|PrefixInvalid) > 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
if contains(dec.text, "BAD_LOCK_PREFIX", "GENERAL_ERROR") && countExactPrefix(inst, PrefixLOCK|PrefixImplicit) > 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
if text == "lock" && size == 1 && contains(dec.text, "BAD_LOCK_PREFIX") {
|
||||
return true
|
||||
}
|
||||
|
||||
// Instructions not known to us.
|
||||
if (contains(text, "error:") || isPrefix(text) && size == 1) && contains(dec.text, unsupported...) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Instructions not known to xed.
|
||||
if contains(text, xedUnsupported...) && contains(dec.text, "ERROR") {
|
||||
return true
|
||||
}
|
||||
|
||||
if (contains(text, "error:") || isPrefix(text) && size == 1) && contains(dec.text, "shl ") && (inst.Opcode>>16)&0xEC38 == 0xC030 {
|
||||
return true
|
||||
}
|
||||
|
||||
// 82 11 22: xed says 'adc byte ptr [ecx], 0x22' but there is no justification in the manuals for that.
|
||||
// C0 30 11: xed says 'shl byte ptr [eax], 0x11' but there is no justification in the manuals for that.
|
||||
// F6 08 11: xed says 'test byte ptr [eax], 0x11' but there is no justification in the manuals for that.
|
||||
if (contains(text, "error:") || isPrefix(text) && size == 1) && hasByte(dec.enc[:dec.nenc], 0x82, 0xC0, 0xC1, 0xD0, 0xD1, 0xD2, 0xD3, 0xF6, 0xF7) {
|
||||
return true
|
||||
}
|
||||
|
||||
// F3 11 22 and many others: xed allows and drops misused rep/repn prefix.
|
||||
if (text == "rep" && dec.enc[0] == 0xF3 || (text == "repn" || text == "repne") && dec.enc[0] == 0xF2) && (!contains(dec.text, "ins", "outs", "movs", "lods", "cmps", "scas") || contains(dec.text, "xmm")) {
|
||||
return true
|
||||
}
|
||||
|
||||
// 0F C7 30: xed says vmptrld qword ptr [eax]; we say rdrand eax.
|
||||
// TODO(rsc): Fix, since we are probably wrong, but we don't have vmptrld in the manual.
|
||||
if contains(text, "rdrand") && contains(dec.text, "vmptrld", "vmxon", "vmclear") {
|
||||
return true
|
||||
}
|
||||
|
||||
// F3 0F AE 00: we say 'rdfsbase dword ptr [eax]' but RDFSBASE needs a register.
|
||||
// Also, this is a 64-bit only instruction.
|
||||
// TODO(rsc): Fix to reject this encoding.
|
||||
if contains(text, "rdfsbase", "rdgsbase", "wrfsbase", "wrgsbase") && contains(dec.text, "ERROR") {
|
||||
return true
|
||||
}
|
||||
|
||||
// 0F 01 F8: we say swapgs but that's only valid in 64-bit mode.
|
||||
// TODO(rsc): Fix.
|
||||
if contains(text, "swapgs") {
|
||||
return true
|
||||
}
|
||||
|
||||
// 0F 24 11: 'mov ecx, tr2' except there is no TR2.
|
||||
// Or maybe the MOV to TR registers doesn't use RMF.
|
||||
if contains(text, "cr1", "cr5", "cr6", "cr7", "tr0", "tr1", "tr2", "tr3", "tr4", "tr5", "tr6", "tr7") && contains(dec.text, "ERROR") {
|
||||
return true
|
||||
}
|
||||
|
||||
// 0F 19 11, 0F 1C 11, 0F 1D 11, 0F 1E 11, 0F 1F 11: xed says nop,
|
||||
// but the Intel manuals say that the only NOP there is 0F 1F /0.
|
||||
// Perhaps xed is reporting an older encoding.
|
||||
if (contains(text, "error:") || isPrefix(text) && size == 1) && contains(dec.text, "nop ") && (inst.Opcode>>8)&0xFFFF38 != 0x0F1F00 {
|
||||
return true
|
||||
}
|
||||
|
||||
// 66 0F AE 38: clflushopt but we only know clflush
|
||||
if contains(text, "clflush") && contains(dec.text, "clflushopt") {
|
||||
return true
|
||||
}
|
||||
|
||||
// 0F 20 04 11: MOV SP, CR0 but has mod!=3 despite register argument.
|
||||
// (This encoding ignores the mod bits.) The decoder sees the non-register
|
||||
// mod and reads farther ahead to decode the memory reference that
|
||||
// isn't really there, causing the size to be too large.
|
||||
// TODO(rsc): Fix.
|
||||
if text == dec.text && size > dec.nenc && contains(text, " cr", " dr", " tr") {
|
||||
return true
|
||||
}
|
||||
|
||||
// 0F AE E9: xed says lfence, which is wrong (only 0F AE E8 is lfence). And so on.
|
||||
if contains(dec.text, "fence") && hasByte(dec.enc[:dec.nenc], 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF) {
|
||||
return true
|
||||
}
|
||||
|
||||
// DD C9, DF C9: xed says 'fxch st0, st1' but that instruction is D9 C9.
|
||||
if (contains(text, "error:") || isPrefix(text) && size == 1) && contains(dec.text, "fxch ") && hasByte(dec.enc[:dec.nenc], 0xDD, 0xDF) {
|
||||
return true
|
||||
}
|
||||
|
||||
// DC D4: xed says 'fcom st0, st4' but that instruction is D8 D4.
|
||||
if (contains(text, "error:") || isPrefix(text) && size == 1) && contains(dec.text, "fcom ") && hasByte(dec.enc[:dec.nenc], 0xD8, 0xDC) {
|
||||
return true
|
||||
}
|
||||
|
||||
// DE D4: xed says 'fcomp st0, st4' but that instruction is D8 D4.
|
||||
if (contains(text, "error:") || isPrefix(text) && size == 1) && contains(dec.text, "fcomp ") && hasByte(dec.enc[:dec.nenc], 0xDC, 0xDE) {
|
||||
return true
|
||||
}
|
||||
|
||||
// DF D4: xed says 'fstp st4, st0' but that instruction is DD D4.
|
||||
if (contains(text, "error:") || isPrefix(text) && size == 1) && contains(dec.text, "fstp ") && hasByte(dec.enc[:dec.nenc], 0xDF) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func countExactPrefix(inst *Inst, target Prefix) int {
|
||||
n := 0
|
||||
for _, p := range inst.Prefix {
|
||||
if p == target {
|
||||
n++
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func hasByte(src []byte, target ...byte) bool {
|
||||
for _, b := range target {
|
||||
if bytes.IndexByte(src, b) >= 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Instructions known to us but not to xed.
|
||||
var xedUnsupported = strings.Fields(`
|
||||
xrstor
|
||||
xsave
|
||||
xsave
|
||||
ud1
|
||||
xgetbv
|
||||
xsetbv
|
||||
fxsave
|
||||
fxrstor
|
||||
clflush
|
||||
lfence
|
||||
mfence
|
||||
sfence
|
||||
rsqrtps
|
||||
rcpps
|
||||
emms
|
||||
ldmxcsr
|
||||
stmxcsr
|
||||
movhpd
|
||||
movnti
|
||||
rdrand
|
||||
movbe
|
||||
movlpd
|
||||
sysret
|
||||
`)
|
|
@ -0,0 +1,205 @@
|
|||
package x86asm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// xed binary from Intel sde-external-6.22.0-2014-03-06.
|
||||
const xedPath = "/Users/rsc/bin/xed"
|
||||
|
||||
func testXedArch(t *testing.T, arch int, generate func(func([]byte))) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping xed test in short mode")
|
||||
}
|
||||
if _, err := os.Stat(xedPath); err != nil {
|
||||
t.Skip(err)
|
||||
}
|
||||
|
||||
testExtDis(t, "intel", arch, xed, generate, allowedMismatchXed)
|
||||
}
|
||||
|
||||
func testXed32(t *testing.T, generate func(func([]byte))) {
|
||||
testXedArch(t, 32, generate)
|
||||
}
|
||||
|
||||
func testXed64(t *testing.T, generate func(func([]byte))) {
|
||||
testXedArch(t, 64, generate)
|
||||
}
|
||||
|
||||
func xed(ext *ExtDis) error {
|
||||
b, err := ext.Run(xedPath, fmt.Sprintf("-%d", ext.Arch), "-n", "1G", "-ir", ext.File.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nmatch := 0
|
||||
next := uint32(start)
|
||||
var (
|
||||
addr uint32
|
||||
encbuf [32]byte
|
||||
enc []byte
|
||||
text string
|
||||
)
|
||||
|
||||
var xedEnd = []byte("# end of text section")
|
||||
var xedEnd1 = []byte("# Errors")
|
||||
|
||||
eof := false
|
||||
for {
|
||||
line, err := b.ReadSlice('\n')
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return fmt.Errorf("reading objdump output: %v", err)
|
||||
}
|
||||
if debug {
|
||||
os.Stdout.Write(line)
|
||||
}
|
||||
if bytes.HasPrefix(line, xedEnd) || bytes.HasPrefix(line, xedEnd1) {
|
||||
eof = true
|
||||
}
|
||||
if eof {
|
||||
continue
|
||||
}
|
||||
nmatch++
|
||||
addr, enc, text = parseLineXed(line, encbuf[:0])
|
||||
if addr > next {
|
||||
return fmt.Errorf("address out of sync expected <= %#x at %q in:\n%s", next, line, line)
|
||||
}
|
||||
if addr < next {
|
||||
continue
|
||||
}
|
||||
switch text {
|
||||
case "repz":
|
||||
text = "rep"
|
||||
case "repnz":
|
||||
text = "repn"
|
||||
default:
|
||||
text = strings.Replace(text, "repz ", "rep ", -1)
|
||||
text = strings.Replace(text, "repnz ", "repn ", -1)
|
||||
}
|
||||
if m := pcrelw.FindStringSubmatch(text); m != nil {
|
||||
targ, _ := strconv.ParseUint(m[2], 16, 64)
|
||||
text = fmt.Sprintf("%s .%+#x", m[1], int16(uint32(targ)-uint32(uint16(addr))-uint32(len(enc))))
|
||||
}
|
||||
if m := pcrel.FindStringSubmatch(text); m != nil {
|
||||
targ, _ := strconv.ParseUint(m[2], 16, 64)
|
||||
text = fmt.Sprintf("%s .%+#x", m[1], int32(uint32(targ)-addr-uint32(len(enc))))
|
||||
}
|
||||
ext.Dec <- ExtInst{addr, encbuf, len(enc), text}
|
||||
encbuf = [32]byte{}
|
||||
enc = nil
|
||||
next += 32
|
||||
}
|
||||
if next != start+uint32(ext.Size) {
|
||||
return fmt.Errorf("not enough results found [%d %d]", next, start+ext.Size)
|
||||
}
|
||||
if err := ext.Wait(); err != nil {
|
||||
return fmt.Errorf("exec: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
xedInRaw = []byte("In raw...")
|
||||
xedDots = []byte("...")
|
||||
xdis = []byte("XDIS ")
|
||||
xedError = []byte("ERROR: ")
|
||||
xedNoDecode = []byte("Could not decode at offset: 0x")
|
||||
)
|
||||
|
||||
func parseLineXed(line []byte, encstart []byte) (addr uint32, enc []byte, text string) {
|
||||
oline := line
|
||||
if bytes.HasPrefix(line, xedInRaw) || bytes.HasPrefix(line, xedDots) {
|
||||
return 0, nil, ""
|
||||
}
|
||||
if bytes.HasPrefix(line, xedError) {
|
||||
i := bytes.IndexByte(line[len(xedError):], ' ')
|
||||
if i < 0 {
|
||||
log.Fatalf("cannot parse error: %q", oline)
|
||||
}
|
||||
errstr := string(line[len(xedError):])
|
||||
i = bytes.Index(line, xedNoDecode)
|
||||
if i < 0 {
|
||||
log.Fatalf("cannot parse error: %q", oline)
|
||||
}
|
||||
i += len(xedNoDecode)
|
||||
j := bytes.IndexByte(line[i:], ' ')
|
||||
if j < 0 {
|
||||
log.Fatalf("cannot parse error: %q", oline)
|
||||
}
|
||||
x, err := strconv.ParseUint(string(trimSpace(line[i:i+j])), 16, 32)
|
||||
if err != nil {
|
||||
log.Fatalf("cannot parse disassembly: %q", oline)
|
||||
}
|
||||
addr = uint32(x)
|
||||
return addr, nil, errstr
|
||||
}
|
||||
|
||||
if !bytes.HasPrefix(line, xdis) {
|
||||
log.Fatalf("cannot parse disassembly: %q", oline)
|
||||
}
|
||||
|
||||
i := bytes.IndexByte(line, ':')
|
||||
if i < 0 {
|
||||
log.Fatalf("cannot parse disassembly: %q", oline)
|
||||
}
|
||||
x, err := strconv.ParseUint(string(trimSpace(line[len(xdis):i])), 16, 32)
|
||||
if err != nil {
|
||||
log.Fatalf("cannot parse disassembly: %q", oline)
|
||||
}
|
||||
addr = uint32(x)
|
||||
|
||||
// spaces
|
||||
i++
|
||||
for i < len(line) && line[i] == ' ' {
|
||||
i++
|
||||
}
|
||||
// instruction class, spaces
|
||||
for i < len(line) && line[i] != ' ' {
|
||||
i++
|
||||
}
|
||||
for i < len(line) && line[i] == ' ' {
|
||||
i++
|
||||
}
|
||||
// instruction set, spaces
|
||||
for i < len(line) && line[i] != ' ' {
|
||||
i++
|
||||
}
|
||||
for i < len(line) && line[i] == ' ' {
|
||||
i++
|
||||
}
|
||||
|
||||
// hex
|
||||
hexStart := i
|
||||
for i < len(line) && line[i] != ' ' {
|
||||
i++
|
||||
}
|
||||
hexEnd := i
|
||||
for i < len(line) && line[i] == ' ' {
|
||||
i++
|
||||
}
|
||||
|
||||
// text
|
||||
textStart := i
|
||||
for i < len(line) && line[i] != '\n' {
|
||||
i++
|
||||
}
|
||||
textEnd := i
|
||||
|
||||
enc, ok := parseHex(line[hexStart:hexEnd], encstart)
|
||||
if !ok {
|
||||
log.Fatalf("cannot parse disassembly: %q", oline)
|
||||
}
|
||||
|
||||
return addr, enc, string(fixSpace(line[textStart:textEnd]))
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"comment": "",
|
||||
"ignore": "",
|
||||
"package": [
|
||||
{
|
||||
"checksumSHA1": "S5YH6+pAjKFAFidprYjhl9OpWWE=",
|
||||
"path": "github.com/brahma-adshonor/gohook",
|
||||
"revision": "cfeffdca9b9b432fd22853f9dea6bc0957c64bcc",
|
||||
"revisionTime": "2020-05-11T02:27:46Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "9+Rs09zfc0ajMWczTSADvj5oUXg=",
|
||||
"path": "github.com/go-logr/logr/testing",
|
||||
"revision": "ff9374eda70c55592ad54d87839c59db58257d2f",
|
||||
"revisionTime": "2020-09-04T21:25:47Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "LuFv4/jlrmFNnDb/5SCSEPAM9vU=",
|
||||
"origin": "github.com/stretchr/testify/vendor/github.com/pmezard/go-difflib/difflib",
|
||||
"path": "github.com/pmezard/go-difflib/difflib",
|
||||
"revision": "221dbe5ed46703ee255b1da0dec05086f5035f62",
|
||||
"revisionTime": "2019-05-17T17:51:56Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "OHK0GAgeySXMMiqH/84b80WoPNU=",
|
||||
"path": "github.com/stretchr/testify/assert",
|
||||
"revision": "221dbe5ed46703ee255b1da0dec05086f5035f62",
|
||||
"revisionTime": "2019-05-17T17:51:56Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "AuAGph7pQZKYtiuSB6ZVroWxlY4=",
|
||||
"path": "golang.org/x/arch/x86/x86asm",
|
||||
"revision": "b19915210f009e139b20abfd6a6052c7acc1f445",
|
||||
"revisionTime": "2020-08-18T21:29:11Z"
|
||||
}
|
||||
],
|
||||
"rootPath": "github.com/fluid-cloudnative/fluid"
|
||||
}
|
Loading…
Reference in New Issue