You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
521 lines
17 KiB
521 lines
17 KiB
/*
|
|
* Copyright 2002-2019 Intel Corporation.
|
|
*
|
|
* This software is provided to you as Sample Source Code as defined in the accompanying
|
|
* End User License Agreement for the Intel(R) Software Development Products ("Agreement")
|
|
* section 1.L.
|
|
*
|
|
* This software and the related documents are provided as is, with no express or implied
|
|
* warranties, other than those that are expressly stated in the License.
|
|
*/
|
|
|
|
/*! @file
|
|
*
|
|
* This file contains a tool that traces all uses and changes to the FS and GS
|
|
* segments, which are typically used to implement thread-local storage on x86
|
|
* platforms. Currently, this tool only knows about the segment-related system
|
|
* calls on Linux.
|
|
*
|
|
* This tool also check:
|
|
* - IARG_REG_REFERENCE/IARG_REG_CONST_REFERENCE to a segment register works (segment selector)
|
|
* and that it is equal to the same segment register when being passed by value.
|
|
* - IARG_REG_VALUE, IARG_REG_CONST_REFERENCE and IARG_CONST_CONTEXT for segment base address
|
|
* (virtual register REG_SEG_GS_BASE/REG_SEG_FS_BASE) works and that their values are equal.
|
|
*
|
|
* Possible to add these checks:
|
|
* - IARG_REG_REFERENCE/IARG_REG_CONST_REFERENCE and IARG_REG_VALUE for segment for segment register return the correct value where applicable (32 Linux).
|
|
* - IARG_REG_VALUE and IARG_REG_CONST_REFERENCE for segment base address (virtual register REG_SEG_GS_BASE/REG_SEG_FS_BASE) return the correct value
|
|
* which the thread area (TLS) (Currently TLS address is not tracked in this tool, if we want enable this check we need
|
|
* to add code to this tool that will track the TLS address)
|
|
* Currently verified these by looking at the logs.
|
|
*/
|
|
|
|
#include "pin.H"
|
|
#include "pending_syscalls.H"
|
|
#include "disasm_container.H"
|
|
|
|
#include <iostream>
|
|
#include <ostream>
|
|
#include <fstream>
|
|
#include <iomanip>
|
|
#include <sstream>
|
|
#include <map>
|
|
using std::string;
|
|
|
|
#if defined(TARGET_LINUX)
|
|
# include <sys/syscall.h>
|
|
# include <asm/ldt.h>
|
|
#endif
|
|
|
|
#if defined(TARGET_LINUX) && defined(TARGET_IA32E)
|
|
# include <asm/prctl.h>
|
|
#endif
|
|
|
|
// These constants are not defined on old kernels.
|
|
//
|
|
#ifndef __NR_set_thread_area
|
|
# define __NR_set_thread_area 243
|
|
#endif
|
|
#ifndef __NR_get_thread_area
|
|
# define __NR_get_thread_area 244
|
|
#endif
|
|
#ifndef SYS_set_thread_area
|
|
# define SYS_set_thread_area __NR_set_thread_area
|
|
#endif
|
|
#ifndef SYS_get_thread_area
|
|
# define SYS_get_thread_area __NR_get_thread_area
|
|
#endif
|
|
#ifndef CLONE_SETTLS
|
|
# define CLONE_SETTLS 0x00080000
|
|
#endif
|
|
|
|
// Specifies a segment descriptor entry. Used with modify_ldt(), etc.
|
|
//
|
|
struct USER_DESC
|
|
{
|
|
UINT32 entry_number;
|
|
ADDRINT base_addr;
|
|
UINT32 limit;
|
|
UINT32 seg_32bit:1;
|
|
UINT32 contents:2;
|
|
UINT32 read_exec_only:1;
|
|
UINT32 limit_in_pages:1;
|
|
UINT32 seg_not_present:1;
|
|
UINT32 useable:1;
|
|
};
|
|
|
|
KNOB<string> KnobOutputFile(KNOB_MODE_WRITEONCE, "pintool", "o", "segtrace.out", "trace file");
|
|
|
|
std::ofstream Out;
|
|
PENDING_SYSCALLS *PendingSyscalls; // Holds syscall information between "before" and "after" instrumentation
|
|
DISASM_CONTAINER *Disassemblies; // Holds disassemblies for "interesting" instructions
|
|
|
|
|
|
static VOID Instruction(INS, VOID *);
|
|
static BOOL WritesSegment(INS, REG *);
|
|
static VOID OnSegReference(UINT32, ADDRINT, ADDRINT*, ADDRINT, THREADID);
|
|
static VOID OnSegGsOrFsReference(UINT32, ADDRINT, ADDRINT*, ADDRINT, ADDRINT*, ADDRINT, THREADID);
|
|
static VOID OnSegWriteBefore(UINT32, UINT16, ADDRINT*, ADDRINT, THREADID);
|
|
static VOID OnSegWriteAfter(UINT32, UINT16, ADDRINT*, ADDRINT*, ADDRINT, THREADID);
|
|
static VOID OnSegWriteAfterContext(CONTEXT*);
|
|
static VOID OnSyscallBefore(ADDRINT, ADDRINT, ADDRINT, ADDRINT, ADDRINT, ADDRINT, ADDRINT, ADDRINT, ADDRINT, THREADID);
|
|
static VOID OnSyscallAfter(ADDRINT, ADDRINT, ADDRINT, ADDRINT, THREADID);
|
|
static std::string Header(THREADID, ADDRINT);
|
|
static std::string PasteDisassembly(const std::string &, const std::string &);
|
|
static std::string SegName(REG);
|
|
static std::string SegSelector(ADDRINT);
|
|
static VOID SyscallEntry(THREADID threadIndex, CONTEXT *ctxt, SYSCALL_STANDARD std, VOID *v);
|
|
static VOID SyscallExit(THREADID threadIndex, CONTEXT *ctxt, SYSCALL_STANDARD std, VOID *v);
|
|
|
|
#if defined(TARGET_LINUX)
|
|
static std::string UserDesc(USER_DESC *);
|
|
#endif
|
|
|
|
#if defined(TARGET_LINUX) && defined(TARGET_IA32E)
|
|
static std::string PrctlFunc(ADDRINT);
|
|
#endif
|
|
|
|
|
|
int main(int argc, char * argv[])
|
|
{
|
|
PIN_Init(argc, argv);
|
|
|
|
Out.open(KnobOutputFile.Value().c_str());
|
|
PendingSyscalls = new PENDING_SYSCALLS();
|
|
Disassemblies = new DISASM_CONTAINER();
|
|
|
|
INS_AddInstrumentFunction(Instruction, 0);
|
|
PIN_AddSyscallEntryFunction(SyscallEntry, 0);
|
|
PIN_AddSyscallExitFunction(SyscallExit, 0);
|
|
|
|
PIN_StartProgram();
|
|
return 0;
|
|
}
|
|
|
|
static VOID Instruction(INS ins, VOID *v)
|
|
{
|
|
REG seg;
|
|
if (INS_SegmentPrefix(ins))
|
|
{
|
|
seg = INS_SegmentRegPrefix(ins);
|
|
if ( (seg == REG_SEG_GS) || (seg == REG_SEG_FS) )
|
|
{
|
|
REG segBaseReg = (seg == REG_SEG_GS)? REG_SEG_GS_BASE : REG_SEG_FS_BASE;
|
|
// Using IARG_REG_CONST_REFERENCE and not IARG_REG_CONST_REFERENCE for segment here since in 64 bits
|
|
// you cannot write back to a segment register in user level
|
|
INS_InsertCall(ins, IPOINT_BEFORE, AFUNPTR(OnSegGsOrFsReference), IARG_UINT32, seg,
|
|
IARG_REG_VALUE, seg, IARG_REG_CONST_REFERENCE, seg, IARG_REG_VALUE, segBaseReg, IARG_REG_CONST_REFERENCE, segBaseReg,
|
|
IARG_INST_PTR, IARG_THREAD_ID, IARG_END);
|
|
}
|
|
else
|
|
{
|
|
// See above comment about IARG_REG_CONST_REFERENCE
|
|
INS_InsertCall(ins, IPOINT_BEFORE, AFUNPTR(OnSegReference), IARG_UINT32, seg,
|
|
IARG_REG_VALUE, seg, IARG_REG_CONST_REFERENCE, seg, IARG_INST_PTR, IARG_THREAD_ID, IARG_END);
|
|
}
|
|
Disassemblies->Add(INS_Address(ins), INS_Disassemble(ins));
|
|
}
|
|
|
|
if (WritesSegment(ins, &seg))
|
|
{
|
|
INS_InsertCall(ins, IPOINT_BEFORE, AFUNPTR(OnSegWriteBefore), IARG_UINT32, seg, IARG_REG_VALUE, seg, IARG_REG_CONST_REFERENCE, seg,
|
|
IARG_INST_PTR, IARG_THREAD_ID, IARG_END);
|
|
|
|
INS_InsertCall(ins, IPOINT_AFTER, AFUNPTR(OnSegWriteAfterContext), IARG_CONST_CONTEXT, IARG_END);
|
|
|
|
// This is only possible in Linux 32 bits so using IARG_REG_REFERENCE for segment is allowed (although not changing value)
|
|
INS_InsertCall(ins, IPOINT_AFTER, AFUNPTR(OnSegWriteAfter), IARG_UINT32, seg, IARG_REG_VALUE, seg, IARG_REG_REFERENCE, seg,
|
|
IARG_REG_CONST_REFERENCE, REG_SEG_GS_BASE, IARG_INST_PTR, IARG_THREAD_ID, IARG_END);
|
|
|
|
Disassemblies->Add(INS_Address(ins), INS_Disassemble(ins));
|
|
}
|
|
|
|
// For O/S's (macOS*) that don't support PIN_AddSyscallEntryFunction(),
|
|
// instrument the system call instruction.
|
|
//
|
|
if (INS_IsSyscall(ins) && INS_IsValidForIpointAfter(ins))
|
|
{
|
|
INS_InsertCall(ins, IPOINT_BEFORE, AFUNPTR(OnSyscallBefore), IARG_SYSCALL_NUMBER, IARG_SYSARG_VALUE, 0,
|
|
IARG_SYSARG_VALUE, 1, IARG_SYSARG_VALUE, 2, IARG_SYSARG_VALUE, 3, IARG_SYSARG_VALUE, 4,
|
|
IARG_REG_VALUE, REG_SEG_FS, IARG_REG_VALUE, REG_SEG_GS, IARG_INST_PTR, IARG_THREAD_ID, IARG_END);
|
|
|
|
INS_InsertCall(ins, IPOINT_AFTER, AFUNPTR(OnSyscallAfter), IARG_SYSRET_VALUE,
|
|
IARG_REG_VALUE, REG_SEG_FS, IARG_REG_VALUE, REG_SEG_GS, IARG_INST_PTR, IARG_THREAD_ID, IARG_END);
|
|
}
|
|
}
|
|
|
|
static BOOL WritesSegment(INS ins, REG *seg)
|
|
{
|
|
if (INS_RegWContain(ins, REG_SEG_FS))
|
|
{
|
|
*seg = REG_SEG_FS;
|
|
return TRUE;
|
|
}
|
|
if (INS_RegWContain(ins, REG_SEG_GS))
|
|
{
|
|
*seg = REG_SEG_GS;
|
|
return TRUE;
|
|
}
|
|
if (INS_RegWContain(ins, REG_SEG_ES))
|
|
{
|
|
*seg = REG_SEG_ES;
|
|
return TRUE;
|
|
}
|
|
if (INS_RegWContain(ins, REG_SEG_CS))
|
|
{
|
|
*seg = REG_SEG_CS;
|
|
return TRUE;
|
|
}
|
|
if (INS_RegWContain(ins, REG_SEG_DS))
|
|
{
|
|
*seg = REG_SEG_DS;
|
|
return TRUE;
|
|
}
|
|
if (INS_RegWContain(ins, REG_SEG_SS))
|
|
{
|
|
*seg = REG_SEG_SS;
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static VOID OnSegReference(UINT32 ireg, ADDRINT val, ADDRINT* seg_ref, ADDRINT pc, THREADID tid)
|
|
{
|
|
REG reg = static_cast<REG>(ireg);
|
|
std::ostringstream s;
|
|
s << Header(tid, pc) << "reference via " << SegName(reg) << " " << SegSelector((UINT16)val);
|
|
Out << PasteDisassembly(s.str(), Disassemblies->Get(pc)) << std::endl;
|
|
|
|
// Verifying segment selector returned by value and by reference match
|
|
assert((UINT16)val == (UINT16)*seg_ref);
|
|
}
|
|
|
|
static VOID OnSegGsOrFsReference(UINT32 ireg, ADDRINT val, ADDRINT* seg_ref, ADDRINT segBaseAddr, ADDRINT* segBaseAddrRef, ADDRINT pc, THREADID tid)
|
|
{
|
|
REG reg = static_cast<REG>(ireg);
|
|
std::ostringstream s;
|
|
s << Header(tid, pc) << "reference via " << SegName(reg) << " " << SegSelector((UINT16)val) << " Segment base address (TLS) "<< std::hex << "0x" << segBaseAddr;
|
|
Out << PasteDisassembly(s.str(), Disassemblies->Get(pc)) << std::endl;
|
|
|
|
// Verifying segment selector returned by value and by reference match
|
|
assert((UINT16)val == (UINT16)*seg_ref);
|
|
|
|
// Verifying thread area (TLS) is not 0.
|
|
assert(segBaseAddr != 0);
|
|
|
|
// Verifying GS/FS segment base address selector returned by value and by reference match
|
|
assert(segBaseAddr == *segBaseAddrRef);
|
|
}
|
|
|
|
static VOID OnSegWriteBefore(UINT32 ireg, UINT16 val, ADDRINT* seg_ref, ADDRINT pc, THREADID tid)
|
|
{
|
|
REG reg = static_cast<REG>(ireg);
|
|
std::ostringstream s;
|
|
s << Header(tid, pc) << "modify " << SegName(reg) << "=" << SegSelector(val);
|
|
Out << PasteDisassembly(s.str(), Disassemblies->Get(pc)) << std::endl;
|
|
|
|
// Verifying segment selector returned by value and by reference match
|
|
assert((UINT16)val == (UINT16)*seg_ref);
|
|
}
|
|
|
|
static VOID OnSegWriteAfterContext(CONTEXT* ctxt)
|
|
{
|
|
ADDRINT gsbase = PIN_GetContextReg(ctxt, REG_SEG_GS_BASE);
|
|
|
|
// Verifying thread area (TLS) is not 0.
|
|
assert(gsbase != 0);
|
|
}
|
|
|
|
static VOID OnSegWriteAfter(UINT32 ireg, UINT16 val, ADDRINT* seg_ref, ADDRINT* segBaseAddrRef, ADDRINT pc, THREADID tid)
|
|
{
|
|
REG reg = static_cast<REG>(ireg);
|
|
std::ostringstream s;
|
|
s << Header(tid, pc) << "modify " << SegName(reg) << "=" << SegSelector(val) << " Segment base address (TLS) "<< std::hex << "0x" << *segBaseAddrRef;
|
|
Out << PasteDisassembly(s.str(), Disassemblies->Get(pc)) << std::endl;
|
|
|
|
// Verifying segment selector returned by value and by reference match
|
|
assert((UINT16)val == (UINT16)*seg_ref);
|
|
|
|
// Verifying thread area (TLS) is not 0.
|
|
assert(*segBaseAddrRef != 0);
|
|
}
|
|
|
|
static VOID OnSyscallBefore(ADDRINT num, ADDRINT arg1, ADDRINT arg2, ADDRINT arg3, ADDRINT arg4, ADDRINT arg5,
|
|
ADDRINT fs, ADDRINT gs, ADDRINT pc, THREADID tid)
|
|
{
|
|
PendingSyscalls->Add(tid, PENDING_SYSCALL(fs, gs, num, arg1, arg2, arg3, arg4, arg5));
|
|
|
|
switch (num)
|
|
{
|
|
#if defined(TARGET_LINUX)
|
|
case SYS_modify_ldt:
|
|
{
|
|
int func = static_cast<int>(arg1);
|
|
if (func == 1)
|
|
{
|
|
USER_DESC *tls = reinterpret_cast<USER_DESC *>(arg2);
|
|
Out << Header(tid, pc) << "modify_ldt(WRITE, " << UserDesc(tls) << ", 0x" <<
|
|
std::hex << arg3 << ")" << std::endl;
|
|
}
|
|
else
|
|
{
|
|
Out << Header(tid, pc) << "modify_ldt(READ, 0x" << std::hex << arg2 <<
|
|
", 0x" << arg3 << ")" << std::endl;
|
|
}
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
#if defined(TARGET_LINUX) && defined(TARGET_IA32E)
|
|
case SYS_arch_prctl:
|
|
Out << Header(tid, pc) << "arch_prctl(" << PrctlFunc(arg1) << ", 0x" << std::hex << arg2 << ")" << std::endl;
|
|
break;
|
|
|
|
case SYS_clone:
|
|
{
|
|
int flags = static_cast<int>(arg1);
|
|
if (flags & CLONE_SETTLS)
|
|
Out << Header(tid, pc) << "clone(CLONE_SETTLS, 0x" << std::hex << arg5 << ")" << std::endl;
|
|
break;
|
|
}
|
|
|
|
case SYS_set_thread_area:
|
|
Out << Header(tid, pc) << "UNEXPECTED: set_thread_area" << std::endl;
|
|
break;
|
|
|
|
case SYS_get_thread_area:
|
|
Out << Header(tid, pc) << "UNEXPECTED: get_thread_area" << std::endl;
|
|
break;
|
|
#endif
|
|
|
|
#if defined(TARGET_LINUX) && defined(TARGET_IA32)
|
|
case SYS_set_thread_area:
|
|
{
|
|
USER_DESC *tls = reinterpret_cast<USER_DESC *>(arg1);
|
|
Out << Header(tid, pc) << "set_thread_area(" << UserDesc(tls) << ")" << std::endl;
|
|
break;
|
|
}
|
|
|
|
case SYS_get_thread_area:
|
|
{
|
|
USER_DESC *tls = reinterpret_cast<USER_DESC *>(arg1);
|
|
Out << Header(tid, pc) << "get_thread_area([entry_number=0x" << std::hex << tls->entry_number << "])" << std::endl;
|
|
break;
|
|
}
|
|
|
|
case SYS_clone:
|
|
{
|
|
int flags = static_cast<int>(arg1);
|
|
if (flags & CLONE_SETTLS)
|
|
{
|
|
USER_DESC *tls = reinterpret_cast<USER_DESC *>(arg4);
|
|
Out << Header(tid, pc) << "clone(CLONE_SETTLS, " << UserDesc(tls) << ")" << std::endl;
|
|
}
|
|
break;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static VOID OnSyscallAfter(ADDRINT ret, ADDRINT fs, ADDRINT gs, ADDRINT pc, THREADID tid)
|
|
{
|
|
PENDING_SYSCALL pend;
|
|
if (!PendingSyscalls->Remove(tid, &pend))
|
|
return;
|
|
|
|
switch (pend._number)
|
|
{
|
|
#if defined(TARGET_LINUX)
|
|
case SYS_modify_ldt:
|
|
if (ret == ADDRINT(-1))
|
|
Out << Header(tid, pc) << "=>modify_ldt(FAILED)" << std::endl;
|
|
break;
|
|
#endif
|
|
|
|
#if defined(TARGET_LINUX) && defined(TARGET_IA32E)
|
|
case SYS_arch_prctl:
|
|
if (ret == ADDRINT(-1))
|
|
Out << Header(tid, pc) << "=>arch_prctl(FAILED)" << std::endl;
|
|
break;
|
|
#endif
|
|
|
|
#if defined(TARGET_LINUX) && defined(TARGET_IA32)
|
|
case SYS_set_thread_area:
|
|
if (ret == ADDRINT(-1))
|
|
{
|
|
Out << Header(tid, pc) << "=>set_thread_area(FAILED)" << std::endl;
|
|
}
|
|
else
|
|
{
|
|
USER_DESC *tls = reinterpret_cast<USER_DESC *>(pend._arg1);
|
|
Out << Header(tid, pc) << "=>set_thread_area(" << UserDesc(tls) << ")" << std::endl;
|
|
}
|
|
break;
|
|
|
|
case SYS_get_thread_area:
|
|
if (ret == ADDRINT(-1))
|
|
{
|
|
Out << Header(tid, pc) << "=>get_thread_area(FAILED)" << std::endl;
|
|
}
|
|
else
|
|
{
|
|
USER_DESC *tls = reinterpret_cast<USER_DESC *>(pend._arg1);
|
|
Out << Header(tid, pc) << "=>get_thread_area(" << UserDesc(tls) << ")" << std::endl;
|
|
}
|
|
break;
|
|
#endif
|
|
}
|
|
|
|
if (fs != pend._fs)
|
|
Out << Header(tid, pc) << "syscall modified FS=" << SegSelector(fs) << std::endl;
|
|
if (gs != pend._gs)
|
|
Out << Header(tid, pc) << "syscall modified GS=" << SegSelector(gs) << std::endl;
|
|
}
|
|
|
|
static VOID SyscallEntry(THREADID threadIndex, CONTEXT *ctxt, SYSCALL_STANDARD std, VOID *v)
|
|
{
|
|
OnSyscallBefore(PIN_GetSyscallNumber(ctxt, std),
|
|
PIN_GetSyscallArgument(ctxt, std, 0),
|
|
PIN_GetSyscallArgument(ctxt, std, 1),
|
|
PIN_GetSyscallArgument(ctxt, std, 2),
|
|
PIN_GetSyscallArgument(ctxt, std, 3),
|
|
PIN_GetSyscallArgument(ctxt, std, 4),
|
|
PIN_GetContextReg(ctxt, REG_SEG_FS),
|
|
PIN_GetContextReg(ctxt, REG_SEG_GS),
|
|
PIN_GetContextReg(ctxt, REG_INST_PTR),
|
|
threadIndex);
|
|
}
|
|
|
|
static VOID SyscallExit(THREADID threadIndex, CONTEXT *ctxt, SYSCALL_STANDARD std, VOID *v)
|
|
{
|
|
OnSyscallAfter( PIN_GetSyscallReturn(ctxt, std),
|
|
PIN_GetContextReg(ctxt, REG_SEG_FS),
|
|
PIN_GetContextReg(ctxt, REG_SEG_GS),
|
|
PIN_GetContextReg(ctxt, REG_INST_PTR),
|
|
threadIndex);
|
|
}
|
|
|
|
static std::string Header(THREADID tid, ADDRINT pc)
|
|
{
|
|
std::ostringstream s;
|
|
s << "tid " << std::dec << tid << ", pc 0x" << std::hex << pc << ": ";
|
|
return s.str();
|
|
}
|
|
|
|
static std::string PasteDisassembly(const std::string &body, const std::string &dis)
|
|
{
|
|
std::ostringstream s;
|
|
s << std::left << std::setw(110) << body << dis;
|
|
return s.str();
|
|
}
|
|
|
|
static std::string SegName(REG reg)
|
|
{
|
|
switch (reg)
|
|
{
|
|
case REG_SEG_FS:
|
|
return "FS";
|
|
case REG_SEG_GS:
|
|
return "GS";
|
|
case REG_SEG_ES:
|
|
return "ES";
|
|
case REG_SEG_CS:
|
|
return "CS";
|
|
case REG_SEG_DS:
|
|
return "DS";
|
|
case REG_SEG_SS:
|
|
return "SS";
|
|
default:
|
|
return "OTHER";
|
|
}
|
|
}
|
|
|
|
static std::string SegSelector(ADDRINT val)
|
|
{
|
|
std::ostringstream s;
|
|
s << "[";
|
|
if (val & 4)
|
|
s << "LDT";
|
|
else
|
|
s << "GDT";
|
|
s << " index=" << std::dec << (val >> 3);
|
|
s << " priv=" << std::dec << (val & 3);
|
|
s << "]";
|
|
return s.str();
|
|
}
|
|
|
|
#if defined(TARGET_LINUX)
|
|
static std::string UserDesc(USER_DESC *tls)
|
|
{
|
|
std::ostringstream s;
|
|
s << "[";
|
|
s << "index=" << std::dec << static_cast<INT32>(tls->entry_number);
|
|
s << " base=0x" << std::hex << tls->base_addr;
|
|
s << "]";
|
|
return s.str();
|
|
}
|
|
#endif
|
|
|
|
#if defined(TARGET_LINUX) && defined(TARGET_IA32E)
|
|
static std::string PrctlFunc(ADDRINT fun)
|
|
{
|
|
switch (fun)
|
|
{
|
|
case ARCH_SET_GS:
|
|
return "ARCH_SET_GS";
|
|
case ARCH_SET_FS:
|
|
return "ARCH_SET_FS";
|
|
case ARCH_GET_FS:
|
|
return "ARCH_GET_FS";
|
|
case ARCH_GET_GS:
|
|
return "ARCH_GET_GS";
|
|
default:
|
|
{
|
|
std::ostringstream s;
|
|
s << "0x" << std::hex << fun;
|
|
return s.str();
|
|
}
|
|
}
|
|
}
|
|
#endif
|