928 lines
25 KiB
C++
928 lines
25 KiB
C++
/*
|
|
* Copyright (C) 2014 The Android Open Source Project
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include "gtest/gtest.h"
|
|
|
|
#include <sys/mman.h>
|
|
|
|
#include <iterator>
|
|
#include <string>
|
|
|
|
#include "berberis/assembler/machine_code.h"
|
|
#include "berberis/assembler/x86_32.h"
|
|
#include "berberis/assembler/x86_64.h"
|
|
#include "berberis/base/bit_util.h"
|
|
#include "berberis/base/logging.h"
|
|
#include "berberis/test_utils/scoped_exec_region.h"
|
|
|
|
#if defined(__i386__)
|
|
typedef berberis::x86_32::Assembler CodeEmitter;
|
|
#elif defined(__amd64__)
|
|
typedef berberis::x86_64::Assembler CodeEmitter;
|
|
#else
|
|
#error "Unsupported platform"
|
|
#endif
|
|
|
|
namespace berberis {
|
|
|
|
int Callee() {
|
|
return 239;
|
|
}
|
|
|
|
float FloatFunc(float f1, float f2) {
|
|
return f1 - f2;
|
|
}
|
|
|
|
inline bool CompareCode(const uint8_t* code_template_begin,
|
|
const uint8_t* code_template_end,
|
|
const MachineCode& code) {
|
|
if ((code_template_end - code_template_begin) != static_cast<intptr_t>(code.install_size())) {
|
|
ALOGE("Code size mismatch: %zd != %u",
|
|
code_template_end - code_template_begin,
|
|
code.install_size());
|
|
return false;
|
|
}
|
|
|
|
if (memcmp(code_template_begin, code.AddrAs<uint8_t>(0), code.install_size()) != 0) {
|
|
ALOGE("Code mismatch");
|
|
MachineCode code2;
|
|
code2.AddSequence(code_template_begin, code_template_end - code_template_begin);
|
|
std::string code_str1, code_str2;
|
|
code.AsString(&code_str1);
|
|
code2.AsString(&code_str2);
|
|
ALOGE("assembler generated\n%s\nshall be\n%s", code_str1.c_str(), code_str2.c_str());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#if defined(__i386__)
|
|
|
|
namespace x86_32 {
|
|
|
|
bool AssemblerTest() {
|
|
MachineCode code;
|
|
CodeEmitter assembler(&code);
|
|
assembler.Movl(Assembler::eax, {.base = Assembler::esp, .disp = 4});
|
|
assembler.CmpXchgl({.base = Assembler::esp, .disp = 4}, Assembler::eax);
|
|
assembler.Subl(Assembler::esp, 16);
|
|
assembler.Movl({.base = Assembler::esp}, Assembler::eax);
|
|
assembler.Push(Assembler::esp);
|
|
assembler.Push(0xcccccccc);
|
|
assembler.Pushl({.base = Assembler::esp, .disp = 0x428});
|
|
assembler.Popl({.base = Assembler::esp, .disp = 0x428});
|
|
assembler.Movl(Assembler::ecx, 0xcccccccc);
|
|
assembler.Call(Assembler::ecx);
|
|
assembler.Movl(Assembler::eax, {.base = Assembler::esp, .disp = 8});
|
|
assembler.Addl(Assembler::esp, 24);
|
|
assembler.Ret();
|
|
assembler.Finalize();
|
|
|
|
// clang-format off
|
|
static const uint8_t code_template[] = {
|
|
0x8b, 0x44, 0x24, 0x04, // mov 0x4(%esp),%eax
|
|
0x0f, 0xb1, 0x44, 0x24, 0x04, // cmpxchg 0x4(%esp),%eax
|
|
0x83, 0xec, 0x10, // sub $16, %esp
|
|
0x89, 0x04, 0x24, // mov %eax,(%esp)
|
|
0x54, // push %esp
|
|
0x68, 0xcc, 0xcc, 0xcc, 0xcc, // push $cccccccc
|
|
0xff, 0xb4, 0x24, 0x28, 0x04, 0x00, 0x00, // pushl 0x428(%esp)
|
|
0x8f, 0x84, 0x24, 0x28, 0x04, 0x00, 0x00, // popl 0x428(%esp)
|
|
0xb9, 0xcc, 0xcc, 0xcc, 0xcc, // mov $cccccccc, %ecx
|
|
0xff, 0xd1, // call *%ecx
|
|
0x8b, 0x44, 0x24, 0x08, // mov 0x8(%esp),%eax
|
|
0x83, 0xc4, 0x18, // add $24, %esp
|
|
0xc3 // ret
|
|
};
|
|
// clang-format on
|
|
|
|
if (sizeof(code_template) != code.install_size()) {
|
|
ALOGE("Code size mismatch: %zu != %u", sizeof(code_template), code.install_size());
|
|
return false;
|
|
}
|
|
|
|
if (memcmp(code_template, code.AddrAs<uint8_t>(0), code.install_size()) != 0) {
|
|
ALOGE("Code mismatch");
|
|
MachineCode code2;
|
|
code2.Add(code_template);
|
|
std::string code_str1, code_str2;
|
|
code.AsString(&code_str1);
|
|
code2.AsString(&code_str2);
|
|
ALOGE("assembler generated\n%s\nshall be\n%s", code_str1.c_str(), code_str2.c_str());
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool LabelTest() {
|
|
MachineCode code;
|
|
CodeEmitter as(&code);
|
|
Assembler::Label skip, skip2, back, end;
|
|
as.Call(bit_cast<const void*>(&Callee));
|
|
as.Jmp(skip);
|
|
as.Movl(Assembler::eax, 2);
|
|
as.Bind(&skip);
|
|
as.Addl(Assembler::eax, 8);
|
|
as.Jmp(skip2);
|
|
as.Bind(&back);
|
|
as.Addl(Assembler::eax, 12);
|
|
as.Jmp(end);
|
|
as.Bind(&skip2);
|
|
as.Jmp(back);
|
|
as.Bind(&end);
|
|
as.Ret();
|
|
as.Finalize();
|
|
|
|
ScopedExecRegion exec(&code);
|
|
|
|
int result = exec.get<int()>()();
|
|
return result == 239 + 8 + 12;
|
|
}
|
|
|
|
bool CondTest1() {
|
|
MachineCode code;
|
|
CodeEmitter as(&code);
|
|
as.Movl(Assembler::eax, 0xcccccccc);
|
|
as.Movl(Assembler::edx, {.base = Assembler::esp, .disp = 4}); // arg1.
|
|
as.Movl(Assembler::ecx, {.base = Assembler::esp, .disp = 8}); // arg2.
|
|
as.Cmpl(Assembler::edx, Assembler::ecx);
|
|
as.Setcc(Assembler::Condition::kEqual, Assembler::eax);
|
|
as.Ret();
|
|
as.Finalize();
|
|
|
|
ScopedExecRegion exec(&code);
|
|
|
|
typedef uint32_t TestFunc(int, int);
|
|
auto target_func = exec.get<TestFunc>();
|
|
uint32_t result = target_func(1, 2);
|
|
if (result != 0xcccccc00) {
|
|
ALOGE("Bug in seteq(not equal): %x", result);
|
|
return false;
|
|
}
|
|
result = target_func(-1, -1);
|
|
if (result != 0xcccccc01) {
|
|
ALOGE("Bug in seteq(equal): %x", result);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CondTest2() {
|
|
MachineCode code;
|
|
CodeEmitter as(&code);
|
|
as.Movl(Assembler::edx, {.base = Assembler::esp, .disp = 4}); // arg1.
|
|
as.Movl(Assembler::ecx, {.base = Assembler::esp, .disp = 8}); // arg2.
|
|
as.Xorl(Assembler::eax, Assembler::eax);
|
|
as.Testb(Assembler::edx, Assembler::ecx);
|
|
as.Setcc(Assembler::Condition::kNotZero, Assembler::eax);
|
|
as.Xchgl(Assembler::eax, Assembler::ecx);
|
|
as.Xchgl(Assembler::ecx, Assembler::eax);
|
|
as.Ret();
|
|
as.Finalize();
|
|
|
|
ScopedExecRegion exec(&code);
|
|
|
|
typedef uint32_t TestFunc(int, int);
|
|
auto target_func = exec.get<TestFunc>();
|
|
uint32_t result = target_func(0x11, 1);
|
|
if (result != 0x1) {
|
|
ALOGE("Bug in testb(not zero): %x", result);
|
|
return false;
|
|
}
|
|
result = target_func(0x11, 0x8);
|
|
if (result != 0x0) {
|
|
ALOGE("Bug in testb(zero): %x", result);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool JccTest() {
|
|
MachineCode code;
|
|
CodeEmitter as(&code);
|
|
Assembler::Label equal, above, below, done;
|
|
as.Movl(Assembler::edx, {.base = Assembler::esp, .disp = 4}); // arg1.
|
|
as.Movl(Assembler::ecx, {.base = Assembler::esp, .disp = 8}); // arg2.
|
|
as.Cmpl(Assembler::edx, Assembler::ecx);
|
|
as.Jcc(Assembler::Condition::kEqual, equal);
|
|
as.Jcc(Assembler::Condition::kBelow, below);
|
|
as.Jcc(Assembler::Condition::kAbove, above);
|
|
|
|
as.Movl(Assembler::eax, 13);
|
|
as.Jmp(done);
|
|
|
|
as.Bind(&equal);
|
|
as.Movl(Assembler::eax, 0u);
|
|
as.Jmp(done);
|
|
|
|
as.Bind(&below);
|
|
as.Movl(Assembler::eax, -1);
|
|
as.Jmp(done);
|
|
|
|
as.Bind(&above);
|
|
as.Movl(Assembler::eax, 1);
|
|
as.Jmp(done);
|
|
|
|
as.Bind(&done);
|
|
as.Ret();
|
|
as.Finalize();
|
|
|
|
ScopedExecRegion exec(&code);
|
|
|
|
typedef int TestFunc(int, int);
|
|
auto target_func = exec.get<TestFunc>();
|
|
int result = target_func(1, 1);
|
|
if (result != 0) {
|
|
ALOGE("Bug in jcc(equal): %x", result);
|
|
return false;
|
|
}
|
|
result = target_func(1, 0);
|
|
if (result != 1) {
|
|
ALOGE("Bug in jcc(above): %x", result);
|
|
return false;
|
|
}
|
|
result = target_func(0, 1);
|
|
if (result != -1) {
|
|
ALOGE("Bug in jcc(below): %x", result);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ShiftTest() {
|
|
MachineCode code;
|
|
CodeEmitter as(&code);
|
|
as.Movl(Assembler::eax, {.base = Assembler::esp, .disp = 4});
|
|
as.Shll(Assembler::eax, int8_t{2});
|
|
as.Shrl(Assembler::eax, int8_t{1});
|
|
as.Movl(Assembler::ecx, 3);
|
|
as.ShllByCl(Assembler::eax);
|
|
as.Ret();
|
|
as.Finalize();
|
|
|
|
ScopedExecRegion exec(&code);
|
|
|
|
typedef uint32_t TestFunc(uint32_t);
|
|
uint32_t result = exec.get<TestFunc>()(22);
|
|
return result == (22 << 4);
|
|
}
|
|
|
|
bool LogicTest() {
|
|
MachineCode code;
|
|
CodeEmitter as(&code);
|
|
as.Movl(Assembler::eax, {.base = Assembler::esp, .disp = 4});
|
|
as.Movl(Assembler::ecx, 0x1);
|
|
as.Xorl(Assembler::eax, Assembler::ecx);
|
|
as.Movl(Assembler::ecx, 0xf);
|
|
as.Andl(Assembler::eax, Assembler::ecx);
|
|
as.Ret();
|
|
as.Finalize();
|
|
|
|
ScopedExecRegion exec(&code);
|
|
|
|
typedef uint32_t TestFunc(uint32_t);
|
|
uint32_t result = exec.get<TestFunc>()(239);
|
|
return result == ((239 ^ 1) & 0xf);
|
|
}
|
|
|
|
bool BsrTest() {
|
|
MachineCode code;
|
|
CodeEmitter as(&code);
|
|
|
|
as.Movl(Assembler::ecx, {.base = Assembler::esp, .disp = 4});
|
|
as.Movl(Assembler::edx, 239);
|
|
as.Bsrl(Assembler::eax, Assembler::ecx);
|
|
as.Cmovl(Assembler::Condition::kZero, Assembler::eax, Assembler::edx);
|
|
as.Ret();
|
|
as.Finalize();
|
|
|
|
ScopedExecRegion exec(&code);
|
|
|
|
typedef uint32_t TestFunc(uint32_t arg);
|
|
auto func = exec.get<TestFunc>();
|
|
return func(0) == 239 && func(1 << 15) == 15;
|
|
}
|
|
|
|
bool CallFPTest() {
|
|
MachineCode code;
|
|
CodeEmitter as(&code);
|
|
as.Push(0x3f800000);
|
|
as.Push(0x40000000);
|
|
as.Call(bit_cast<const void*>(&FloatFunc));
|
|
as.Fstps({.base = Assembler::esp});
|
|
as.Pop(Assembler::eax);
|
|
as.Addl(Assembler::esp, 4);
|
|
as.Ret();
|
|
as.Finalize();
|
|
|
|
ScopedExecRegion exec(&code);
|
|
|
|
typedef uint32_t TestFunc();
|
|
uint32_t result = exec.get<TestFunc>()();
|
|
return result == 0x3f800000;
|
|
}
|
|
|
|
bool XmmTest() {
|
|
MachineCode code;
|
|
CodeEmitter as(&code);
|
|
as.Movl(Assembler::eax, 0x3f800000);
|
|
as.Movd(Assembler::xmm0, Assembler::eax);
|
|
as.Movl(Assembler::eax, 0x40000000);
|
|
as.Movd(Assembler::xmm5, Assembler::eax);
|
|
as.Addss(Assembler::xmm0, Assembler::xmm5);
|
|
as.Movd(Assembler::eax, Assembler::xmm0);
|
|
as.Ret();
|
|
as.Finalize();
|
|
|
|
ScopedExecRegion exec(&code);
|
|
|
|
typedef uint32_t TestFunc();
|
|
uint32_t result = exec.get<TestFunc>()();
|
|
return result == 0x40400000;
|
|
}
|
|
|
|
bool ReadGlobalTest() {
|
|
MachineCode code;
|
|
CodeEmitter as(&code);
|
|
static const uint32_t kData[] __attribute__((aligned(16))) = // NOLINT
|
|
{0x00112233, 0x44556677, 0x8899aabb, 0xccddeeff};
|
|
as.Movsd(Assembler::xmm0, {.disp = bit_cast<int32_t>(&kData)});
|
|
as.Movdqa(Assembler::xmm1, {.disp = bit_cast<int32_t>(&kData)});
|
|
as.Movl(Assembler::eax, {.base = Assembler::esp, .disp = 4});
|
|
as.Movl(Assembler::ecx, {.base = Assembler::esp, .disp = 8});
|
|
as.Movsd({.base = Assembler::eax}, Assembler::xmm0);
|
|
as.Movdqu({.base = Assembler::ecx}, Assembler::xmm1);
|
|
|
|
as.Ret();
|
|
as.Finalize();
|
|
|
|
ScopedExecRegion exec(&code);
|
|
|
|
typedef void TestFunc(void*, void*);
|
|
uint8_t res1[8];
|
|
uint8_t res2[16];
|
|
exec.get<TestFunc>()(res1, res2);
|
|
|
|
return (memcmp(res1, kData, 8) == 0) && (memcmp(res2, kData, 16) == 0);
|
|
}
|
|
|
|
} // namespace x86_32
|
|
|
|
#elif defined(__amd64__)
|
|
|
|
namespace x86_64 {
|
|
|
|
bool AssemblerTest() {
|
|
MachineCode code;
|
|
CodeEmitter assembler(&code);
|
|
assembler.Movq(Assembler::rax, Assembler::rdi);
|
|
assembler.Subq(Assembler::rsp, 16);
|
|
assembler.Movq({.base = Assembler::rsp}, Assembler::rax);
|
|
assembler.Movq({.base = Assembler::rsp, .disp = 8}, Assembler::rax);
|
|
assembler.Movl({.base = Assembler::rax, .disp = 16}, 239);
|
|
assembler.Movq(Assembler::r11, {.base = Assembler::rsp});
|
|
assembler.Addq(Assembler::rsp, 16);
|
|
assembler.Ret();
|
|
assembler.Finalize();
|
|
|
|
// clang-format off
|
|
static const uint8_t code_template[] = {
|
|
0x48, 0x89, 0xf8, // mov %rdi, %rax
|
|
0x48, 0x83, 0xec, 0x10, // sub $0x10, %rsp
|
|
0x48, 0x89, 0x04, 0x24, // mov rax, (%rsp)
|
|
0x48, 0x89, 0x44, 0x24, 0x08, // mov rax, 8(%rsp)
|
|
0xc7, 0x40, 0x10, 0xef, 0x00, // movl $239, 0x10(%rax)
|
|
0x00, 0x00,
|
|
0x4c, 0x8b, 0x1c, 0x24, // mov (%rsp), r11
|
|
0x48, 0x83, 0xc4, 0x10, // add $0x10, %rsp
|
|
0xc3 // ret
|
|
};
|
|
// clang-format on
|
|
|
|
if (sizeof(code_template) != code.install_size()) {
|
|
ALOGE("Code size mismatch: %zu != %u", sizeof(code_template), code.install_size());
|
|
return false;
|
|
}
|
|
|
|
if (memcmp(code_template, code.AddrAs<uint8_t>(0), code.install_size()) != 0) {
|
|
ALOGE("Code mismatch");
|
|
MachineCode code2;
|
|
code2.Add(code_template);
|
|
std::string code_str1, code_str2;
|
|
code.AsString(&code_str1);
|
|
code2.AsString(&code_str2);
|
|
ALOGE("assembler generated\n%s\nshall be\n%s", code_str1.c_str(), code_str2.c_str());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool LabelTest() {
|
|
MachineCode code;
|
|
CodeEmitter as(&code);
|
|
Assembler::Label skip, skip2, back, end;
|
|
as.Call(bit_cast<const void*>(&Callee));
|
|
as.Jmp(skip);
|
|
as.Movl(Assembler::rax, 2);
|
|
as.Bind(&skip);
|
|
as.Addb(Assembler::rax, {end});
|
|
as.Jmp(skip2);
|
|
as.Bind(&back);
|
|
as.Addl(Assembler::rax, 12);
|
|
as.Jmp(end);
|
|
as.Bind(&skip2);
|
|
as.Jmp(back);
|
|
as.Bind(&end);
|
|
as.Ret();
|
|
as.Finalize();
|
|
|
|
ScopedExecRegion exec(&code);
|
|
|
|
typedef int TestFunc();
|
|
int result = exec.get<TestFunc>()();
|
|
return result == uint8_t(239 + 0xc3) + 12;
|
|
}
|
|
|
|
bool CondTest1() {
|
|
MachineCode code;
|
|
CodeEmitter as(&code);
|
|
as.Movl(Assembler::rax, 0xcccccccc);
|
|
as.Cmpl(Assembler::rdi, Assembler::rsi);
|
|
as.Setcc(Assembler::Condition::kEqual, Assembler::rax);
|
|
as.Ret();
|
|
as.Finalize();
|
|
|
|
ScopedExecRegion exec(&code);
|
|
|
|
std::string code_str;
|
|
code.AsString(&code_str);
|
|
typedef uint32_t TestFunc(int, int);
|
|
auto target_func = exec.get<TestFunc>();
|
|
uint32_t result;
|
|
result = target_func(1, 2);
|
|
if (result != 0xcccccc00) {
|
|
ALOGE("Bug in seteq(not equal): %x", result);
|
|
return false;
|
|
}
|
|
result = target_func(-1, -1);
|
|
if (result != 0xcccccc01) {
|
|
ALOGE("Bug in seteq(equal): %x", result);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CondTest2() {
|
|
MachineCode code;
|
|
CodeEmitter as(&code);
|
|
as.Movl(Assembler::rdx, Assembler::rdi); // arg1.
|
|
as.Movl(Assembler::rcx, Assembler::rsi); // arg2.
|
|
as.Xorl(Assembler::rax, Assembler::rax);
|
|
as.Testb(Assembler::rdx, Assembler::rcx);
|
|
as.Setcc(Assembler::Condition::kNotZero, Assembler::rax);
|
|
as.Xchgq(Assembler::rax, Assembler::rcx);
|
|
as.Xchgq(Assembler::rcx, Assembler::rax);
|
|
as.Xchgq(Assembler::rcx, Assembler::r11);
|
|
as.Xchgq(Assembler::r11, Assembler::rcx);
|
|
as.Ret();
|
|
as.Finalize();
|
|
|
|
ScopedExecRegion exec(&code);
|
|
|
|
typedef uint32_t TestFunc(int, int);
|
|
auto target_func = exec.get<TestFunc>();
|
|
uint32_t result = target_func(0x11, 1);
|
|
if (result != 0x1) {
|
|
printf("Bug in testb(not zero): %x", result);
|
|
return false;
|
|
}
|
|
result = target_func(0x11, 0x8);
|
|
if (result != 0x0) {
|
|
printf("Bug in testb(zero): %x", result);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool JccTest() {
|
|
MachineCode code;
|
|
CodeEmitter as(&code);
|
|
Assembler::Label equal, above, below, done;
|
|
as.Cmpl(Assembler::rdi, Assembler::rsi);
|
|
as.Jcc(Assembler::Condition::kEqual, equal);
|
|
as.Jcc(Assembler::Condition::kBelow, below);
|
|
as.Jcc(Assembler::Condition::kAbove, above);
|
|
|
|
as.Movl(Assembler::rax, 13);
|
|
as.Jmp(done);
|
|
|
|
as.Bind(&equal);
|
|
as.Movq(Assembler::rax, 0);
|
|
as.Jmp(done);
|
|
|
|
as.Bind(&below);
|
|
as.Movl(Assembler::rax, -1);
|
|
as.Jmp(done);
|
|
|
|
as.Bind(&above);
|
|
as.Movl(Assembler::rax, 1);
|
|
as.Jmp(done);
|
|
|
|
as.Bind(&done);
|
|
as.Ret();
|
|
as.Finalize();
|
|
|
|
ScopedExecRegion exec(&code);
|
|
|
|
typedef int TestFunc(int, int);
|
|
auto target_func = exec.get<TestFunc>();
|
|
int result;
|
|
result = target_func(1, 1);
|
|
if (result != 0) {
|
|
ALOGE("Bug in jcc(equal): %x", result);
|
|
return false;
|
|
}
|
|
result = target_func(1, 0);
|
|
if (result != 1) {
|
|
ALOGE("Bug in jcc(above): %x", result);
|
|
return false;
|
|
}
|
|
result = target_func(0, 1);
|
|
if (result != -1) {
|
|
ALOGE("Bug in jcc(below): %x", result);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ReadWriteTest() {
|
|
MachineCode code;
|
|
CodeEmitter as(&code);
|
|
|
|
as.Movq(Assembler::rax, 0);
|
|
as.Movb(Assembler::rax, {.base = Assembler::rdi});
|
|
as.Movl(Assembler::rcx, {.base = Assembler::rsi});
|
|
as.Addl(Assembler::rax, Assembler::rcx);
|
|
as.Movl({.base = Assembler::rsi}, Assembler::rax);
|
|
as.Ret();
|
|
as.Finalize();
|
|
|
|
ScopedExecRegion exec(&code);
|
|
|
|
typedef uint32_t TestFunc(uint8_t*, uint32_t*);
|
|
uint8_t p1[4] = {0x12, 0x34, 0x56, 0x78};
|
|
uint32_t p2 = 0x239;
|
|
uint32_t result = exec.get<TestFunc>()(p1, &p2);
|
|
return (result == 0x239 + 0x12) && (p2 == result);
|
|
}
|
|
|
|
bool CallFPTest() {
|
|
MachineCode code;
|
|
CodeEmitter as(&code);
|
|
as.Movl(Assembler::rax, 0x40000000);
|
|
as.Movd(Assembler::xmm0, Assembler::rax);
|
|
as.Movl(Assembler::rax, 0x3f800000);
|
|
as.Movd(Assembler::xmm1, Assembler::rax);
|
|
as.Call(bit_cast<const void*>(&FloatFunc));
|
|
as.Movd(Assembler::rax, Assembler::xmm0);
|
|
as.Ret();
|
|
as.Finalize();
|
|
|
|
ScopedExecRegion exec(&code);
|
|
|
|
typedef uint32_t TestFunc();
|
|
uint32_t result = exec.get<TestFunc>()();
|
|
return result == 0x3f800000;
|
|
}
|
|
|
|
bool XmmTest() {
|
|
MachineCode code;
|
|
CodeEmitter as(&code);
|
|
as.Movl(Assembler::rax, 0x40000000);
|
|
as.Movd(Assembler::xmm0, Assembler::rax);
|
|
as.Movl(Assembler::rax, 0x3f800000);
|
|
as.Movd(Assembler::xmm11, Assembler::rax);
|
|
as.Addss(Assembler::xmm0, Assembler::xmm11);
|
|
as.Movaps(Assembler::xmm12, Assembler::xmm0);
|
|
as.Addss(Assembler::xmm0, Assembler::xmm12);
|
|
as.Movapd(Assembler::xmm14, Assembler::xmm1);
|
|
as.Movd(Assembler::rax, Assembler::xmm0);
|
|
as.Ret();
|
|
as.Finalize();
|
|
|
|
ScopedExecRegion exec(&code);
|
|
|
|
typedef uint32_t TestFunc();
|
|
uint32_t result = exec.get<TestFunc>()();
|
|
return result == 0x40c00000;
|
|
}
|
|
|
|
bool XmmMemTest() {
|
|
MachineCode code;
|
|
CodeEmitter as(&code);
|
|
|
|
as.Movsd(Assembler::xmm0, {.base = Assembler::rdi});
|
|
as.Movaps(Assembler::xmm12, Assembler::xmm0);
|
|
as.Addsd(Assembler::xmm12, Assembler::xmm12);
|
|
as.Movsd({.base = Assembler::rdi}, Assembler::xmm12);
|
|
as.Movq(Assembler::rax, Assembler::xmm0);
|
|
as.Ret();
|
|
as.Finalize();
|
|
|
|
ScopedExecRegion exec(&code);
|
|
|
|
double d = 239.0;
|
|
char bits[16], *p = bits + 5;
|
|
memcpy(p, &d, sizeof(d));
|
|
|
|
typedef uint64_t TestFunc(char* p);
|
|
uint64_t result = exec.get<TestFunc>()(p);
|
|
uint64_t doubled = *reinterpret_cast<uint64_t*>(p);
|
|
return result == 0x406de00000000000ULL && doubled == 0x407de00000000000ULL;
|
|
}
|
|
|
|
bool MovsxblRexTest() {
|
|
MachineCode code;
|
|
CodeEmitter as(&code);
|
|
|
|
as.Xorl(Assembler::rdx, Assembler::rdx);
|
|
as.Movl(Assembler::rsi, 0xdeadff);
|
|
// CodeEmitter should use REX prefix to encode SIL.
|
|
// Without REX DH is used.
|
|
as.Movsxbl(Assembler::rax, Assembler::rsi);
|
|
as.Ret();
|
|
as.Finalize();
|
|
|
|
ScopedExecRegion exec(&code);
|
|
|
|
typedef uint32_t TestFunc();
|
|
uint32_t result = exec.get<TestFunc>()();
|
|
|
|
return result == 0xffffffff;
|
|
}
|
|
|
|
bool MovzxblRexTest() {
|
|
MachineCode code;
|
|
CodeEmitter as(&code);
|
|
|
|
as.Xorl(Assembler::rdx, Assembler::rdx);
|
|
as.Movl(Assembler::rsi, 0xdeadff);
|
|
// CodeEmitter should use REX prefix to encode SIL.
|
|
// Without REX DH is used.
|
|
as.Movzxbl(Assembler::rax, Assembler::rsi);
|
|
as.Ret();
|
|
as.Finalize();
|
|
|
|
ScopedExecRegion exec(&code);
|
|
|
|
typedef uint32_t TestFunc();
|
|
uint32_t result = exec.get<TestFunc>()();
|
|
|
|
return result == 0x000000ff;
|
|
}
|
|
|
|
bool ShldlRexTest() {
|
|
MachineCode code;
|
|
CodeEmitter as(&code);
|
|
|
|
as.Movl(Assembler::rdx, 0x12345678);
|
|
// If most-significant bit is not encoded correctly with REX
|
|
// RAX can be used instead R8 and R10 can be used instead RDX.
|
|
// Init them all:
|
|
as.Xorl(Assembler::r8, Assembler::r8);
|
|
as.Movl(Assembler::rax, 0xdeadbeef);
|
|
as.Movl(Assembler::r10, 0xdeadbeef);
|
|
|
|
as.Shldl(Assembler::r8, Assembler::rdx, int8_t{8});
|
|
as.Movl(Assembler::rcx, 8);
|
|
as.ShldlByCl(Assembler::r8, Assembler::rdx);
|
|
|
|
as.Movl(Assembler::rax, Assembler::r8);
|
|
|
|
as.Ret();
|
|
as.Finalize();
|
|
|
|
ScopedExecRegion exec(&code);
|
|
|
|
typedef uint32_t TestFunc();
|
|
uint32_t result = exec.get<TestFunc>()();
|
|
|
|
return result == 0x1212;
|
|
}
|
|
|
|
bool ShrdlRexTest() {
|
|
MachineCode code;
|
|
CodeEmitter as(&code);
|
|
|
|
as.Movl(Assembler::rdx, 0x12345678);
|
|
// If most-significant bit is not encoded correctly with REX
|
|
// RAX can be used instead R8 and R10 can be used instead RDX.
|
|
// Init them all:
|
|
as.Xorl(Assembler::r8, Assembler::r8);
|
|
as.Movl(Assembler::rax, 0xdeadbeef);
|
|
as.Movl(Assembler::r10, 0xdeadbeef);
|
|
|
|
as.Shrdl(Assembler::r8, Assembler::rdx, int8_t{8});
|
|
as.Movl(Assembler::rcx, 8);
|
|
as.ShrdlByCl(Assembler::r8, Assembler::rdx);
|
|
|
|
as.Movl(Assembler::rax, Assembler::r8);
|
|
|
|
as.Ret();
|
|
as.Finalize();
|
|
|
|
ScopedExecRegion exec(&code);
|
|
|
|
typedef uint32_t TestFunc();
|
|
uint32_t result = exec.get<TestFunc>()();
|
|
|
|
return result == 0x78780000;
|
|
}
|
|
|
|
bool ReadGlobalTest() {
|
|
MachineCode code;
|
|
CodeEmitter as(&code);
|
|
static const uint32_t kData[] __attribute__((aligned(16))) = // NOLINT
|
|
{0x00112233, 0x44556677, 0x8899aabb, 0xccddeeff};
|
|
// We couldn't read data from arbitrary address on x86_64, need address in first 2GiB.
|
|
void* data =
|
|
mmap(nullptr, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, -1, 0);
|
|
// Copy our global there.
|
|
memcpy(data, kData, 16);
|
|
int32_t data_offset = static_cast<int32_t>(bit_cast<intptr_t>(data));
|
|
as.Movsd(Assembler::xmm0, {.disp = data_offset});
|
|
as.Movdqa(Assembler::xmm1, {.disp = data_offset});
|
|
as.Movsd({.base = Assembler::rdi}, Assembler::xmm0);
|
|
as.Movdqu({.base = Assembler::rsi}, Assembler::xmm1);
|
|
|
|
as.Ret();
|
|
as.Finalize();
|
|
|
|
ScopedExecRegion exec(&code);
|
|
|
|
typedef void TestFunc(void*, void*);
|
|
uint8_t res1[8];
|
|
uint8_t res2[16];
|
|
exec.get<TestFunc>()(res1, res2);
|
|
|
|
munmap(data, 4096);
|
|
|
|
return (memcmp(res1, kData, 8) == 0) && (memcmp(res2, kData, 16) == 0);
|
|
}
|
|
|
|
bool MemShiftTest() {
|
|
MachineCode code;
|
|
CodeEmitter as(&code);
|
|
|
|
as.Push(Assembler::rdi);
|
|
as.Movl(Assembler::rcx, 1);
|
|
as.ShrlByCl({.base = Assembler::rsp});
|
|
as.Addl(Assembler::rcx, 1);
|
|
as.Movq(Assembler::rdi, Assembler::rsp);
|
|
as.ShllByCl({.base = Assembler::rdi});
|
|
as.Pop(Assembler::rax);
|
|
|
|
as.Ret();
|
|
as.Finalize();
|
|
|
|
ScopedExecRegion exec(&code);
|
|
|
|
typedef int TestFunc(int x);
|
|
int result = exec.get<TestFunc>()(0x10);
|
|
|
|
return result == 0x20;
|
|
}
|
|
|
|
} // namespace x86_64
|
|
|
|
#endif
|
|
|
|
#if defined(__i386__) || defined(__amd64__)
|
|
|
|
#if defined(__i386__)
|
|
|
|
extern "C" const uint8_t berberis_gnu_as_output_start[] asm(
|
|
"berberis_gnu_as_output_start_x86_32");
|
|
extern "C" const uint8_t berberis_gnu_as_output_end[] asm(
|
|
"berberis_gnu_as_output_end_x86_32");
|
|
|
|
namespace x86_32 {
|
|
void GenInsnsCommon(CodeEmitter* as);
|
|
void GenInsnsArch(CodeEmitter* as);
|
|
} // namespace x86_32
|
|
|
|
#else
|
|
|
|
extern "C" const uint8_t berberis_gnu_as_output_start[] asm(
|
|
"berberis_gnu_as_output_start_x86_64");
|
|
extern "C" const uint8_t berberis_gnu_as_output_end[] asm(
|
|
"berberis_gnu_as_output_end_x86_64");
|
|
|
|
namespace x86_64 {
|
|
void GenInsnsCommon(CodeEmitter* as);
|
|
void GenInsnsArch(CodeEmitter* as);
|
|
} // namespace x86_64
|
|
|
|
#endif
|
|
|
|
bool ExhaustiveTest() {
|
|
MachineCode code;
|
|
CodeEmitter as(&code);
|
|
|
|
#if defined(__i386__)
|
|
berberis::x86_32::GenInsnsCommon(&as);
|
|
berberis::x86_32::GenInsnsArch(&as);
|
|
#else
|
|
berberis::x86_64::GenInsnsCommon(&as);
|
|
berberis::x86_64::GenInsnsArch(&as);
|
|
#endif
|
|
as.Finalize();
|
|
|
|
return CompareCode(berberis_gnu_as_output_start, berberis_gnu_as_output_end, code);
|
|
}
|
|
|
|
bool MixedAssembler() {
|
|
MachineCode code;
|
|
x86_32::Assembler as32(&code);
|
|
x86_64::Assembler as64(&code);
|
|
x86_32::Assembler::Label lbl32;
|
|
x86_64::Assembler::Label lbl64;
|
|
|
|
as32.Jmp(lbl32);
|
|
as32.Xchgl(x86_32::Assembler::eax, x86_32::Assembler::eax);
|
|
as64.Jmp(lbl64);
|
|
as64.Xchgl(x86_64::Assembler::rax, x86_64::Assembler::rax);
|
|
as32.Bind(&lbl32);
|
|
as32.Movl(x86_32::Assembler::eax, {.disp = 0});
|
|
as64.Bind(&lbl64);
|
|
as32.Finalize();
|
|
as64.Finalize();
|
|
|
|
// clang-format off
|
|
static const uint8_t code_template[] = {
|
|
0xe9, 0x08, 0x00, 0x00, 0x00, // jmp lbl32
|
|
0x90, // xchg %eax, %eax == nop
|
|
0xe9, 0x07, 0x00, 0x00, 0x00, // jmp lbl64
|
|
0x87, 0xc0, // xchg %eax, %eax != nop
|
|
// lbl32:
|
|
0xa1, 0x00, 0x00, 0x00, 0x00 // movabs %eax, 0x0
|
|
// lbl64:
|
|
};
|
|
// clang-format on
|
|
|
|
return CompareCode(std::begin(code_template), std::end(code_template), code);
|
|
}
|
|
#endif
|
|
|
|
} // namespace berberis
|
|
|
|
TEST(Assembler, AssemblerTest) {
|
|
#if defined(__i386__)
|
|
EXPECT_TRUE(berberis::x86_32::AssemblerTest());
|
|
EXPECT_TRUE(berberis::x86_32::LabelTest());
|
|
EXPECT_TRUE(berberis::x86_32::CondTest1());
|
|
EXPECT_TRUE(berberis::x86_32::CondTest2());
|
|
EXPECT_TRUE(berberis::x86_32::JccTest());
|
|
EXPECT_TRUE(berberis::x86_32::ShiftTest());
|
|
EXPECT_TRUE(berberis::x86_32::LogicTest());
|
|
EXPECT_TRUE(berberis::x86_32::CallFPTest());
|
|
EXPECT_TRUE(berberis::x86_32::XmmTest());
|
|
EXPECT_TRUE(berberis::x86_32::BsrTest());
|
|
EXPECT_TRUE(berberis::x86_32::ReadGlobalTest());
|
|
EXPECT_TRUE(berberis::ExhaustiveTest());
|
|
EXPECT_TRUE(berberis::MixedAssembler());
|
|
#elif defined(__amd64__)
|
|
EXPECT_TRUE(berberis::x86_64::AssemblerTest());
|
|
EXPECT_TRUE(berberis::x86_64::LabelTest());
|
|
EXPECT_TRUE(berberis::x86_64::CondTest1());
|
|
EXPECT_TRUE(berberis::x86_64::CondTest2());
|
|
EXPECT_TRUE(berberis::x86_64::JccTest());
|
|
EXPECT_TRUE(berberis::x86_64::ReadWriteTest());
|
|
EXPECT_TRUE(berberis::x86_64::CallFPTest());
|
|
EXPECT_TRUE(berberis::x86_64::XmmTest());
|
|
EXPECT_TRUE(berberis::x86_64::XmmMemTest());
|
|
EXPECT_TRUE(berberis::x86_64::MovsxblRexTest());
|
|
EXPECT_TRUE(berberis::x86_64::MovzxblRexTest());
|
|
EXPECT_TRUE(berberis::x86_64::ShldlRexTest());
|
|
EXPECT_TRUE(berberis::x86_64::ShrdlRexTest());
|
|
EXPECT_TRUE(berberis::x86_64::ReadGlobalTest());
|
|
EXPECT_TRUE(berberis::x86_64::MemShiftTest());
|
|
EXPECT_TRUE(berberis::ExhaustiveTest());
|
|
EXPECT_TRUE(berberis::MixedAssembler());
|
|
#endif
|
|
}
|