阅读 lldb 源码学习 ptrace 的使用.
来到 lldb/source/Plugins/Process/Linux 目录中.
先看头文件, 了解下实现了那些功能:
发现 NativeProcessLinux 类的构造方法是一个私有方法, 找了一下发现 NativeProcessLinux::Manager 中的 Launch Attach 才是入口, 先看一下 Launch:
整体浏览下来逻辑如下:
然我们仔细看一下实现, 首先是 LaunchProcess, 看看如何创建子进程, 创建后都做了什么:
通过 fork 创建子进程, 并将子进程的启动参数 launch_info 包装为 ForkLaunchInfo 类的实例 fork_launch_info, 再跟入 ChildFunc 函数:
针对启动参数做了很多处理, 其实关键的只有两步:
让我们回来再看接着看 NativeProcessLinux::Manager::Launch 方法, 跟入 SetDefaultPtraceOpts:
SetDefaultPtraceOpts 方法用于设置 ptrace 的选项, 再由 PtraceWrapper 方法调用 ptrace, 并且 PtraceWrapper 方法还封装了一些日志, 错误处理的逻辑.
在回到 NativeProcessLinux::Manager::Launch 跟入 NativeRegisterContextLinux::DetermineArchitecture, 查看获取错误信息的实现, 这里进入了 NativeRegisterContextLinux*.cpp 系列文件, NativeRegisterContextLinux.cpp 与 NativeRegisterContextLinux.h 提供统一接口, 其他文件对应不同架构的实例化.

这个跟了一下流程比较长, 并且与咱们的要探究的 ptrace 使用没什么太大的关系, 不再赘述, 感兴趣可以自己看一下.
再来看一下 NativeProcessLinux::Manager::Attach:
附加一个进程要做的是就是根据这个进程 pid 找到它所有的线程, 然后 PtraceWrapper(PTRACE_ATTACH, tid) 用 ptrace 向所有的线程发送 PTRACE_ATTACH 命令, 此时这个进程会进入被调试状态, 内核向被调试进程中的每个线程发送一个 SIGSTOP 信号使其暂停, 处于 PTRACED 状态, 而调用 ptrace 的进程, 即调试进程等待 SIGCHLD 信号.
再看一下 FindProcessThreads 方法:
相当于这种:

读取 /proc/[pid]/task/ 虚拟文件中的信息, 找到对应 pid 的 tid.
通过向被调试进程发送 SIGSTOP 信号来强制进程暂停, 是调试过程中中断程序运行的常用手段. 调试器后续可以通过 Resume 方法恢复进程执行.
恢复被调试进程中线程的执行, 根据 resume_actions 中指定的动作控制每个线程的行为, 如继续运行、单步执行等. 其中有几个值得细看的方法, 让我们一一探究一下.
首先是 SupportHardwareSingleStepping:
根据机构类型确定是否支持硬件断点, 硬件断点需要硬件支持(这个像是废话), 后面会看如何实现硬件/软件断点. 这个架构类型信息 m_arch 是在 Launch / Attach 中确定的, 具体来说是在编译时通过 cmake 确定的. 可以看一下 llvm 支持的所有架构:
再来看一下 SetupSoftwareSingleStepping:
设置软件单步断点, 实现逻辑主要为:
我们按顺序看一下 ReadMemoryCallback, ReadRegisterCallback(WriteMemoryCallback WriteRegisterCallback 不看, 几乎没有内容):
emulator_baton->m_process.ReadMemory 对应了 NativeProcessLinux 类中的 ReadMemory 方法:
首先尝试用 process_vm_readv 系统调用, 如果不支持, 使用 ptrace 的包装方法 PtraceWrapper 发送 PTRACE_PEEKDATA 命令.
我们可以再看一下 NativeProcessLinux 类中的 ReadMemory 方法:
可以看到还是依赖的 ptrace 的包装方法 PtraceWrapper 发送 PTRACE_POKEDATA 命令.
emulator_baton->m_reg_context.ReadRegister 对应了 NativeRegisterContext 类中的 ReadRegister 虚方法, 对于不同的架构有不同的实现, 这里我们看 lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp:
ReadGPR ReadFPR ReadAllSVE...实现上都差不多:
都是调用 ReadRegisterSet:
可以看到是调用 ptrace 的包装方法 PtraceWrapper 发送 PTRACE_GETREGSET 命令. 同样的在 lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux.cpp 目录中有这样一些列方法:
可以预料的是, 写入寄存器也是依赖 PtraceWrapper 发送命令.
总结来看 读/写 -> 内存/寄存器, 本质上都是依赖 ptrace 提供的命令, 然后做一些错误, 日志, 拷贝...处理.
回到 SetupSoftwareSingleStepping 跟入 emulator_up->EvaluateInstruction:
根据不同的 opcode 调用对应的指令处理方法, 在软件层面解析并模拟执行指令, 而不依赖实际的硬件处理器执行, 有一个很长的对应表:

然后得到计算出新 PC 值. 这个模拟解释执行指令这里涉及的东西比较多, 并且与 ptrace 关系不大, 这个不在详细看了, 感兴趣可以自己根根看.
在回到 SetupSoftwareSingleStepping 跟入 process.SetBreakpoint, 实际是 NativeProcessLinux 类的 SetBreakpoint 方法:
封装了 NativeProcessLinux 类 SetHardwareBreakpoint 与 SetSoftwareBreakpoint 方法, 我们一一分析一下:
跟入 EnableSoftwareBreakpoint:
这里把源代码的提示字符串也写成的中文, 函数执行逻辑如下:
thread->SetHardwareBreakpoint 对应了 NativeThreadLinux 中的 SetHardwareBreakpoint:
跟入 m_reg_context_up->SetHardwareBreakpoint, 我们还是看 arm64 的实现, 在 lldb/source/Plugins/Process/Utility/NativeRegisterContextDBReg_arm64.cpp 文件中:
这个 WriteHardwareDebugRegs 实现在 lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp 中:
实际上是在通过 ptrace 设置 debug 寄存器, 并且做好记录和检测.
最后让我们再回到 Resume 方法, 跟入 ResumeThread:
先来看看 thread.Resume, 对应了 NativeThreadLinux 类的 Resume 方法:
刷新所有与硬件有关的断点, 然后通过 PtraceWrapper 发送 ptrace 的 PTRACE_CONT 命令, 恢复进程运行. (SetWatchpoint 与 SetHardwareBreakpoint 类似, 不再赘述)
再来看看 thread.SingleStep, 对应了 NativeThreadLinux 类的 SingleStep 方法:
硬件单步对应 PTRACE_SINGLESTEP 命令, 软件单步对应 PTRACE_CONT 命令(下一条已在 NativeProcessLinux::Resume 中设被替换为断点指令).
至此我们都旅程结束.
/usr/include/x86_64-linux-gnu/bits/signum-arch.h
一定程度上反映了 ptrace 的功能:
namespace lldb_private {
class Status;
class Scalar;
namespace process_linux {
class NativeProcessLinux : public NativeProcessELF,
private NativeProcessSoftwareSingleStep {
public:
class Manager : public NativeProcessProtocol::Manager {
public:
Manager(MainLoop &mainloop);
llvm::Expected<std::unique_ptr<NativeProcessProtocol>>
Launch(ProcessLaunchInfo &launch_info,
NativeDelegate &native_delegate) override;
llvm::Expected<std::unique_ptr<NativeProcessProtocol>>
Attach(lldb::pid_t pid, NativeDelegate &native_delegate) override;
Extension GetSupportedExtensions() const override;
void AddProcess(NativeProcessLinux &process) {
m_processes.insert(&process);
}
void RemoveProcess(NativeProcessLinux &process) {
m_processes.erase(&process);
}
void CollectThread(::pid_t tid);
private:
MainLoop::SignalHandleUP m_sigchld_handle;
llvm::SmallPtrSet<NativeProcessLinux *, 2> m_processes;
llvm::DenseSet<::pid_t> m_unowned_threads;
void SigchldHandler();
};
~NativeProcessLinux() override { m_manager.RemoveProcess(*this); }
Status Resume(const ResumeActionList &resume_actions) override;
Status Halt() override;
Status Detach() override;
Status Signal(int signo) override;
Status Interrupt() override;
Status Kill() override;
Status GetMemoryRegionInfo(lldb::addr_t load_addr,
MemoryRegionInfo &range_info) override;
Status ReadMemory(lldb::addr_t addr, void *buf, size_t size,
size_t &bytes_read) override;
Status WriteMemory(lldb::addr_t addr, const void *buf, size_t size,
size_t &bytes_written) override;
llvm::Expected<lldb::addr_t> AllocateMemory(size_t size,
uint32_t permissions) override;
llvm::Error DeallocateMemory(lldb::addr_t addr) override;
Status ReadMemoryTags(int32_t type, lldb::addr_t addr, size_t len,
std::vector<uint8_t> &tags) override;
Status WriteMemoryTags(int32_t type, lldb::addr_t addr, size_t len,
const std::vector<uint8_t> &tags) override;
size_t UpdateThreads() override;
const ArchSpec &GetArchitecture() const override { return m_arch; }
Status SetBreakpoint(lldb::addr_t addr, uint32_t size,
bool hardware) override;
Status RemoveBreakpoint(lldb::addr_t addr, bool hardware = false) override;
void DoStopIDBumped(uint32_t newBumpId) override;
Status GetLoadedModuleFileSpec(const char *module_path,
FileSpec &file_spec) override;
Status GetFileLoadAddress(const llvm::StringRef &file_name,
lldb::addr_t &load_addr) override;
NativeThreadLinux *GetThreadByID(lldb::tid_t id);
NativeThreadLinux *GetCurrentThread();
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>>
GetAuxvData() const override {
return getProcFile(GetID(), "auxv");
}
llvm::Error TraceStart(llvm::StringRef json_request,
llvm::StringRef type) override;
llvm::Error TraceStop(const TraceStopRequest &request) override;
llvm::Expected<llvm::json::Value>
TraceGetState(llvm::StringRef type) override;
llvm::Expected<std::vector<uint8_t>>
TraceGetBinaryData(const TraceGetBinaryDataRequest &request) override;
llvm::Expected<TraceSupportedResponse> TraceSupported() override;
static Status PtraceWrapper(int req, lldb::pid_t pid, void *addr = nullptr,
void *data = nullptr, size_t data_size = 0,
long *result = nullptr);
bool SupportHardwareSingleStepping() const;
Status GetSignalInfo(lldb::tid_t tid, void *siginfo) const;
protected:
llvm::Expected<llvm::ArrayRef<uint8_t>>
GetSoftwareBreakpointTrapOpcode(size_t size_hint) override;
llvm::Expected<uint64_t> Syscall(llvm::ArrayRef<uint64_t> args);
private:
Manager &m_manager;
ArchSpec m_arch;
LazyBool m_supports_mem_region = eLazyBoolCalculate;
std::vector<std::pair<MemoryRegionInfo, FileSpec>> m_mem_region_cache;
lldb::tid_t m_pending_notification_tid = LLDB_INVALID_THREAD_ID;
llvm::DenseMap<lldb::addr_t, lldb::addr_t> m_allocated_memory;
NativeProcessLinux(::pid_t pid, int terminal_fd, NativeDelegate &delegate,
const ArchSpec &arch, Manager &manager,
llvm::ArrayRef<::pid_t> tids);
static llvm::Expected<std::vector<::pid_t>> Attach(::pid_t pid);
static Status SetDefaultPtraceOpts(const lldb::pid_t);
bool TryHandleWaitStatus(lldb::pid_t pid, WaitStatus status);
void MonitorCallback(NativeThreadLinux &thread, WaitStatus status);
void MonitorSIGTRAP(const siginfo_t &info, NativeThreadLinux &thread);
void MonitorTrace(NativeThreadLinux &thread);
void MonitorBreakpoint(NativeThreadLinux &thread);
void MonitorWatchpoint(NativeThreadLinux &thread, uint32_t wp_index);
void MonitorSignal(const siginfo_t &info, NativeThreadLinux &thread);
bool HasThreadNoLock(lldb::tid_t thread_id);
void StopTrackingThread(NativeThreadLinux &thread);
NativeThreadLinux &AddThread(lldb::tid_t thread_id, bool resume);
Status NotifyTracersOfNewThread(lldb::tid_t tid);
Status NotifyTracersOfThreadDestroyed(lldb::tid_t tid);
void NotifyTracersProcessWillResume() override;
void NotifyTracersProcessDidStop() override;
Status GetEventMessage(lldb::tid_t tid, unsigned long *message);
void NotifyThreadDeath(lldb::tid_t tid);
Status Detach(lldb::tid_t tid);
void StopRunningThreads(lldb::tid_t triggering_tid);
void SignalIfAllThreadsStopped();
Status ResumeThread(NativeThreadLinux &thread, lldb::StateType state,
int signo);
void ThreadWasCreated(NativeThreadLinux &thread);
void SigchldHandler();
Status PopulateMemoryRegionCache();
IntelPTCollector m_intel_pt_collector;
bool MonitorClone(NativeThreadLinux &parent, lldb::pid_t child_pid,
int event);
};
}
}
namespace lldb_private {
class Status;
class Scalar;
namespace process_linux {
class NativeProcessLinux : public NativeProcessELF,
private NativeProcessSoftwareSingleStep {
public:
class Manager : public NativeProcessProtocol::Manager {
public:
Manager(MainLoop &mainloop);
llvm::Expected<std::unique_ptr<NativeProcessProtocol>>
Launch(ProcessLaunchInfo &launch_info,
NativeDelegate &native_delegate) override;
llvm::Expected<std::unique_ptr<NativeProcessProtocol>>
Attach(lldb::pid_t pid, NativeDelegate &native_delegate) override;
Extension GetSupportedExtensions() const override;
void AddProcess(NativeProcessLinux &process) {
m_processes.insert(&process);
}
void RemoveProcess(NativeProcessLinux &process) {
m_processes.erase(&process);
}
void CollectThread(::pid_t tid);
private:
MainLoop::SignalHandleUP m_sigchld_handle;
llvm::SmallPtrSet<NativeProcessLinux *, 2> m_processes;
llvm::DenseSet<::pid_t> m_unowned_threads;
void SigchldHandler();
};
~NativeProcessLinux() override { m_manager.RemoveProcess(*this); }
Status Resume(const ResumeActionList &resume_actions) override;
Status Halt() override;
Status Detach() override;
Status Signal(int signo) override;
Status Interrupt() override;
Status Kill() override;
Status GetMemoryRegionInfo(lldb::addr_t load_addr,
MemoryRegionInfo &range_info) override;
Status ReadMemory(lldb::addr_t addr, void *buf, size_t size,
size_t &bytes_read) override;
Status WriteMemory(lldb::addr_t addr, const void *buf, size_t size,
size_t &bytes_written) override;
llvm::Expected<lldb::addr_t> AllocateMemory(size_t size,
uint32_t permissions) override;
llvm::Error DeallocateMemory(lldb::addr_t addr) override;
Status ReadMemoryTags(int32_t type, lldb::addr_t addr, size_t len,
std::vector<uint8_t> &tags) override;
Status WriteMemoryTags(int32_t type, lldb::addr_t addr, size_t len,
const std::vector<uint8_t> &tags) override;
size_t UpdateThreads() override;
const ArchSpec &GetArchitecture() const override { return m_arch; }
Status SetBreakpoint(lldb::addr_t addr, uint32_t size,
bool hardware) override;
Status RemoveBreakpoint(lldb::addr_t addr, bool hardware = false) override;
void DoStopIDBumped(uint32_t newBumpId) override;
Status GetLoadedModuleFileSpec(const char *module_path,
FileSpec &file_spec) override;
Status GetFileLoadAddress(const llvm::StringRef &file_name,
lldb::addr_t &load_addr) override;
NativeThreadLinux *GetThreadByID(lldb::tid_t id);
NativeThreadLinux *GetCurrentThread();
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>>
GetAuxvData() const override {
return getProcFile(GetID(), "auxv");
}
llvm::Error TraceStart(llvm::StringRef json_request,
llvm::StringRef type) override;
llvm::Error TraceStop(const TraceStopRequest &request) override;
llvm::Expected<llvm::json::Value>
TraceGetState(llvm::StringRef type) override;
llvm::Expected<std::vector<uint8_t>>
TraceGetBinaryData(const TraceGetBinaryDataRequest &request) override;
llvm::Expected<TraceSupportedResponse> TraceSupported() override;
static Status PtraceWrapper(int req, lldb::pid_t pid, void *addr = nullptr,
void *data = nullptr, size_t data_size = 0,
long *result = nullptr);
bool SupportHardwareSingleStepping() const;
Status GetSignalInfo(lldb::tid_t tid, void *siginfo) const;
protected:
llvm::Expected<llvm::ArrayRef<uint8_t>>
GetSoftwareBreakpointTrapOpcode(size_t size_hint) override;
llvm::Expected<uint64_t> Syscall(llvm::ArrayRef<uint64_t> args);
private:
Manager &m_manager;
ArchSpec m_arch;
LazyBool m_supports_mem_region = eLazyBoolCalculate;
std::vector<std::pair<MemoryRegionInfo, FileSpec>> m_mem_region_cache;
lldb::tid_t m_pending_notification_tid = LLDB_INVALID_THREAD_ID;
llvm::DenseMap<lldb::addr_t, lldb::addr_t> m_allocated_memory;
NativeProcessLinux(::pid_t pid, int terminal_fd, NativeDelegate &delegate,
const ArchSpec &arch, Manager &manager,
llvm::ArrayRef<::pid_t> tids);
static llvm::Expected<std::vector<::pid_t>> Attach(::pid_t pid);
static Status SetDefaultPtraceOpts(const lldb::pid_t);
bool TryHandleWaitStatus(lldb::pid_t pid, WaitStatus status);
void MonitorCallback(NativeThreadLinux &thread, WaitStatus status);
void MonitorSIGTRAP(const siginfo_t &info, NativeThreadLinux &thread);
void MonitorTrace(NativeThreadLinux &thread);
void MonitorBreakpoint(NativeThreadLinux &thread);
void MonitorWatchpoint(NativeThreadLinux &thread, uint32_t wp_index);
void MonitorSignal(const siginfo_t &info, NativeThreadLinux &thread);
bool HasThreadNoLock(lldb::tid_t thread_id);
void StopTrackingThread(NativeThreadLinux &thread);
NativeThreadLinux &AddThread(lldb::tid_t thread_id, bool resume);
Status NotifyTracersOfNewThread(lldb::tid_t tid);
Status NotifyTracersOfThreadDestroyed(lldb::tid_t tid);
void NotifyTracersProcessWillResume() override;
void NotifyTracersProcessDidStop() override;
Status GetEventMessage(lldb::tid_t tid, unsigned long *message);
void NotifyThreadDeath(lldb::tid_t tid);
Status Detach(lldb::tid_t tid);
void StopRunningThreads(lldb::tid_t triggering_tid);
void SignalIfAllThreadsStopped();
Status ResumeThread(NativeThreadLinux &thread, lldb::StateType state,
int signo);
void ThreadWasCreated(NativeThreadLinux &thread);
void SigchldHandler();
Status PopulateMemoryRegionCache();
IntelPTCollector m_intel_pt_collector;
bool MonitorClone(NativeThreadLinux &parent, lldb::pid_t child_pid,
int event);
};
}
}
llvm::Expected<std::unique_ptr<NativeProcessProtocol>>
NativeProcessLinux::Manager::Launch(ProcessLaunchInfo &launch_info,
NativeDelegate &native_delegate) {
Log *log = GetLog(POSIXLog::Process);
MaybeLogLaunchInfo(launch_info);
Status status;
::pid_t pid = ProcessLauncherPosixFork()
.LaunchProcess(launch_info, status)
.GetProcessId();
LLDB_LOG(log, "pid = {0:x}", pid);
if (status.Fail()) {
LLDB_LOG(log, "failed to launch process: {0}", status);
return status.ToError();
}
int wstatus = 0;
::pid_t wpid = llvm::sys::RetryAfterSignal(-1, ::waitpid, pid, &wstatus, 0);
assert(wpid == pid);
(void)wpid;
if (!WIFSTOPPED(wstatus)) {
LLDB_LOG(log, "Could not sync with inferior process: wstatus={1}",
WaitStatus::Decode(wstatus));
return llvm::make_error<StringError>("Could not sync with inferior process",
llvm::inconvertibleErrorCode());
}
LLDB_LOG(log, "inferior started, now in stopped state");
status = SetDefaultPtraceOpts(pid);
if (status.Fail()) {
LLDB_LOG(log, "failed to set default ptrace options: {0}", status);
return status.ToError();
}
llvm::Expected<ArchSpec> arch_or =
NativeRegisterContextLinux::DetermineArchitecture(pid);
if (!arch_or)
return arch_or.takeError();
return std::unique_ptr<NativeProcessLinux>(new NativeProcessLinux(
pid,
launch_info.GetPTY().ReleasePrimaryFileDescriptor(),
native_delegate,
*arch_or,
*this,
{pid}
));
}
llvm::Expected<std::unique_ptr<NativeProcessProtocol>>
NativeProcessLinux::Manager::Launch(ProcessLaunchInfo &launch_info,
NativeDelegate &native_delegate) {
Log *log = GetLog(POSIXLog::Process);
MaybeLogLaunchInfo(launch_info);
Status status;
::pid_t pid = ProcessLauncherPosixFork()
.LaunchProcess(launch_info, status)
.GetProcessId();
LLDB_LOG(log, "pid = {0:x}", pid);
if (status.Fail()) {
LLDB_LOG(log, "failed to launch process: {0}", status);
return status.ToError();
}
int wstatus = 0;
::pid_t wpid = llvm::sys::RetryAfterSignal(-1, ::waitpid, pid, &wstatus, 0);
assert(wpid == pid);
(void)wpid;
if (!WIFSTOPPED(wstatus)) {
LLDB_LOG(log, "Could not sync with inferior process: wstatus={1}",
WaitStatus::Decode(wstatus));
return llvm::make_error<StringError>("Could not sync with inferior process",
llvm::inconvertibleErrorCode());
}
LLDB_LOG(log, "inferior started, now in stopped state");
status = SetDefaultPtraceOpts(pid);
if (status.Fail()) {
LLDB_LOG(log, "failed to set default ptrace options: {0}", status);
return status.ToError();
}
llvm::Expected<ArchSpec> arch_or =
NativeRegisterContextLinux::DetermineArchitecture(pid);
if (!arch_or)
return arch_or.takeError();
return std::unique_ptr<NativeProcessLinux>(new NativeProcessLinux(
pid,
launch_info.GetPTY().ReleasePrimaryFileDescriptor(),
native_delegate,
*arch_or,
*this,
{pid}
));
}
HostProcess
ProcessLauncherPosixFork::LaunchProcess(const ProcessLaunchInfo &launch_info,
Status &error) {
PipePosix pipe;
const bool child_processes_inherit = false;
error = pipe.CreateNew(child_processes_inherit);
if (error.Fail())
return HostProcess();
const ForkLaunchInfo fork_launch_info(launch_info);
::pid_t pid = ::fork();
if (pid == -1) {
error.SetErrorStringWithFormatv("Fork failed with error message: {0}",
llvm::sys::StrError());
return HostProcess(LLDB_INVALID_PROCESS_ID);
}
if (pid == 0) {
pipe.CloseReadFileDescriptor();
ChildFunc(pipe.ReleaseWriteFileDescriptor(), fork_launch_info);
}
pipe.CloseWriteFileDescriptor();
llvm::SmallString<0> buf;
size_t pos = 0;
ssize_t r = 0;
do {
pos += r;
buf.resize_for_overwrite(pos + 100);
r = llvm::sys::RetryAfterSignal(-1, read, pipe.GetReadFileDescriptor(),
buf.begin() + pos, buf.size() - pos);
} while (r > 0);
assert(r != -1);
buf.resize(pos);
if (buf.empty())
return HostProcess(pid);
error.SetErrorString(buf);
llvm::sys::RetryAfterSignal(-1, waitpid, pid, nullptr, 0);
return HostProcess();
}
HostProcess
ProcessLauncherPosixFork::LaunchProcess(const ProcessLaunchInfo &launch_info,
Status &error) {
PipePosix pipe;
const bool child_processes_inherit = false;
error = pipe.CreateNew(child_processes_inherit);
if (error.Fail())
return HostProcess();
const ForkLaunchInfo fork_launch_info(launch_info);
::pid_t pid = ::fork();
if (pid == -1) {
error.SetErrorStringWithFormatv("Fork failed with error message: {0}",
llvm::sys::StrError());
return HostProcess(LLDB_INVALID_PROCESS_ID);
}
if (pid == 0) {
pipe.CloseReadFileDescriptor();
ChildFunc(pipe.ReleaseWriteFileDescriptor(), fork_launch_info);
}
pipe.CloseWriteFileDescriptor();
llvm::SmallString<0> buf;
size_t pos = 0;
ssize_t r = 0;
do {
pos += r;
buf.resize_for_overwrite(pos + 100);
r = llvm::sys::RetryAfterSignal(-1, read, pipe.GetReadFileDescriptor(),
buf.begin() + pos, buf.size() - pos);
} while (r > 0);
assert(r != -1);
buf.resize(pos);
if (buf.empty())
return HostProcess(pid);
error.SetErrorString(buf);
llvm::sys::RetryAfterSignal(-1, waitpid, pid, nullptr, 0);
return HostProcess();
}
[[noreturn]] static void ChildFunc(int error_fd, const ForkLaunchInfo &info) {
if (info.separate_process_group) {
if (setpgid(0, 0) != 0)
ExitWithError(error_fd, "setpgid");
}
for (const ForkFileAction &action : info.actions) {
switch (action.action) {
case FileAction::eFileActionClose:
if (close(action.fd) != 0)
ExitWithError(error_fd, "close");
break;
case FileAction::eFileActionDuplicate:
if (dup2(action.fd, action.arg) == -1)
ExitWithError(error_fd, "dup2");
break;
case FileAction::eFileActionOpen:
DupDescriptor(error_fd, action.path.c_str(), action.fd, action.arg);
break;
case FileAction::eFileActionNone:
break;
}
}
if (!info.wd.empty() && 0 != ::chdir(info.wd.c_str()))
ExitWithError(error_fd, "chdir");
if (info.disable_aslr)
DisableASLR(error_fd);
sigset_t set;
if (sigemptyset(&set) != 0 ||
pthread_sigmask(SIG_SETMASK, &set, nullptr) != 0)
ExitWithError(error_fd, "pthread_sigmask");
if (info.debug) {
if (setgid(getgid()) != 0)
ExitWithError(error_fd, "setgid");
const llvm::StringRef proc_fd_path = "/proc/self/fd";
std::error_code ec;
bool result;
ec = llvm::sys::fs::is_directory(proc_fd_path, result);
if (result) {
std::vector<int> files_to_close;
for (llvm::sys::fs::directory_iterator iter(proc_fd_path, ec), file_end;
iter != file_end && !ec; iter.increment(ec)) {
int fd = std::stoi(iter->path().substr(proc_fd_path.size() + 1));
if (fd > 2 && !info.has_action(fd) && fd != error_fd)
files_to_close.push_back(fd);
}
for (int file_to_close : files_to_close)
close(file_to_close);
} else {
int max_fd = sysconf(_SC_OPEN_MAX);
for (int fd = 3; fd < max_fd; ++fd)
if (!info.has_action(fd) && fd != error_fd)
close(fd);
}
if (ptrace(PT_TRACE_ME, 0, nullptr, 0) == -1)
ExitWithError(error_fd, "ptrace");
}
execve(info.argv[0], const_cast<char *const *>(info.argv), info.envp);
#if defined(__linux__)
if (errno == ETXTBSY) {
usleep(50000);
execve(info.argv[0], const_cast<char *const *>(info.argv), info.envp);
}
#endif
ExitWithError(error_fd, "execve");
}
[[noreturn]] static void ChildFunc(int error_fd, const ForkLaunchInfo &info) {
if (info.separate_process_group) {
if (setpgid(0, 0) != 0)
ExitWithError(error_fd, "setpgid");
}
for (const ForkFileAction &action : info.actions) {
switch (action.action) {
case FileAction::eFileActionClose:
if (close(action.fd) != 0)
ExitWithError(error_fd, "close");
break;
case FileAction::eFileActionDuplicate:
if (dup2(action.fd, action.arg) == -1)
ExitWithError(error_fd, "dup2");
break;
case FileAction::eFileActionOpen:
DupDescriptor(error_fd, action.path.c_str(), action.fd, action.arg);
break;
case FileAction::eFileActionNone:
break;
}
}
if (!info.wd.empty() && 0 != ::chdir(info.wd.c_str()))
ExitWithError(error_fd, "chdir");
if (info.disable_aslr)
DisableASLR(error_fd);
sigset_t set;
if (sigemptyset(&set) != 0 ||
pthread_sigmask(SIG_SETMASK, &set, nullptr) != 0)
ExitWithError(error_fd, "pthread_sigmask");
if (info.debug) {
if (setgid(getgid()) != 0)
ExitWithError(error_fd, "setgid");
const llvm::StringRef proc_fd_path = "/proc/self/fd";
std::error_code ec;
bool result;
ec = llvm::sys::fs::is_directory(proc_fd_path, result);
if (result) {
std::vector<int> files_to_close;
for (llvm::sys::fs::directory_iterator iter(proc_fd_path, ec), file_end;
iter != file_end && !ec; iter.increment(ec)) {
int fd = std::stoi(iter->path().substr(proc_fd_path.size() + 1));
if (fd > 2 && !info.has_action(fd) && fd != error_fd)
files_to_close.push_back(fd);
}
for (int file_to_close : files_to_close)
close(file_to_close);
} else {
int max_fd = sysconf(_SC_OPEN_MAX);
for (int fd = 3; fd < max_fd; ++fd)
if (!info.has_action(fd) && fd != error_fd)
close(fd);
}
if (ptrace(PT_TRACE_ME, 0, nullptr, 0) == -1)
ExitWithError(error_fd, "ptrace");
}
execve(info.argv[0], const_cast<char *const *>(info.argv), info.envp);
#if defined(__linux__)
if (errno == ETXTBSY) {
usleep(50000);
execve(info.argv[0], const_cast<char *const *>(info.argv), info.envp);
}
#endif
ExitWithError(error_fd, "execve");
}
Status NativeProcessLinux::SetDefaultPtraceOpts(lldb::pid_t pid) {
long ptrace_opts = 0;
ptrace_opts |= PTRACE_O_TRACEEXIT;
ptrace_opts |= PTRACE_O_TRACECLONE;
ptrace_opts |= PTRACE_O_TRACEEXEC;
ptrace_opts |= PTRACE_O_TRACEFORK;
ptrace_opts |= PTRACE_O_TRACEVFORK;
ptrace_opts |= PTRACE_O_TRACEVFORKDONE;
return PtraceWrapper(PTRACE_SETOPTIONS, pid, nullptr, (void *)ptrace_opts);
}
Status NativeProcessLinux::PtraceWrapper(int req, lldb::pid_t pid, void *addr,
void *data, size_t data_size,
long *result) {
Status error;
long int ret;
Log *log = GetLog(POSIXLog::Ptrace);
PtraceDisplayBytes(req, data, data_size);
errno = 0;
if (req == PTRACE_GETREGSET || req == PTRACE_SETREGSET)
ret = ptrace(static_cast<__ptrace_request>(req), static_cast<::pid_t>(pid),
*(unsigned int *)addr, data);
else
ret = ptrace(static_cast<__ptrace_request>(req), static_cast<::pid_t>(pid),
addr, data);
if (ret == -1)
error.SetErrorToErrno();
if (result)
*result = ret;
LLDB_LOG(log, "ptrace({0}, {1}, {2}, {3}, {4})={5:x}", req, pid, addr, data,
data_size, ret);
PtraceDisplayBytes(req, data, data_size);
if (error.Fail())
LLDB_LOG(log, "ptrace() failed: {0}", error);
return error;
}
Status NativeProcessLinux::SetDefaultPtraceOpts(lldb::pid_t pid) {
long ptrace_opts = 0;
ptrace_opts |= PTRACE_O_TRACEEXIT;
ptrace_opts |= PTRACE_O_TRACECLONE;
ptrace_opts |= PTRACE_O_TRACEEXEC;
ptrace_opts |= PTRACE_O_TRACEFORK;
ptrace_opts |= PTRACE_O_TRACEVFORK;
ptrace_opts |= PTRACE_O_TRACEVFORKDONE;
return PtraceWrapper(PTRACE_SETOPTIONS, pid, nullptr, (void *)ptrace_opts);
}
Status NativeProcessLinux::PtraceWrapper(int req, lldb::pid_t pid, void *addr,
void *data, size_t data_size,
long *result) {
Status error;
long int ret;
Log *log = GetLog(POSIXLog::Ptrace);
PtraceDisplayBytes(req, data, data_size);
errno = 0;
if (req == PTRACE_GETREGSET || req == PTRACE_SETREGSET)
ret = ptrace(static_cast<__ptrace_request>(req), static_cast<::pid_t>(pid),
*(unsigned int *)addr, data);
else
ret = ptrace(static_cast<__ptrace_request>(req), static_cast<::pid_t>(pid),
addr, data);
if (ret == -1)
error.SetErrorToErrno();
if (result)
*result = ret;
LLDB_LOG(log, "ptrace({0}, {1}, {2}, {3}, {4})={5:x}", req, pid, addr, data,
data_size, ret);
PtraceDisplayBytes(req, data, data_size);
if (error.Fail())
LLDB_LOG(log, "ptrace() failed: {0}", error);
return error;
}
llvm::Expected<std::unique_ptr<NativeProcessProtocol>>
NativeProcessLinux::Manager::Attach(
lldb::pid_t pid, NativeProcessProtocol::NativeDelegate &native_delegate) {
Log *log = GetLog(POSIXLog::Process);
LLDB_LOG(log, "pid = {0:x}", pid);
auto tids_or = NativeProcessLinux::Attach(pid);
if (!tids_or)
return tids_or.takeError();
ArrayRef<::pid_t> tids = *tids_or;
llvm::Expected<ArchSpec> arch_or =
NativeRegisterContextLinux::DetermineArchitecture(tids[0]);
if (!arch_or)
return arch_or.takeError();
return std::unique_ptr<NativeProcessLinux>(
new NativeProcessLinux(pid, -1, native_delegate, *arch_or, *this, tids));
}
llvm::Expected<std::vector<::pid_t>> NativeProcessLinux::Attach(::pid_t pid) {
Log *log = GetLog(POSIXLog::Process);
Status status;
Host::TidMap tids_to_attach;
while (Host::FindProcessThreads(pid, tids_to_attach)) {
for (Host::TidMap::iterator it = tids_to_attach.begin();
it != tids_to_attach.end();) {
if (it->second == false) {
lldb::tid_t tid = it->first;
if ((status = PtraceWrapper(PTRACE_ATTACH, tid)).Fail()) {
if (status.GetError() == ESRCH) {
it = tids_to_attach.erase(it);
continue;
}
if (status.GetError() == EPERM) {
return AddPtraceScopeNote(status.ToError());
}
return status.ToError();
}
int wpid =
llvm::sys::RetryAfterSignal(-1, ::waitpid, tid, nullptr, __WALL);
if (wpid < 0) {
if (errno == ESRCH) {
it = tids_to_attach.erase(it);
continue;
}
return llvm::errorCodeToError(
std::error_code(errno, std::generic_category()));
}
if ((status = SetDefaultPtraceOpts(tid)).Fail())
return status.ToError();
LLDB_LOG(log, "adding tid = {0}", tid);
it->second = true;
}
++it;
}
}
size_t tid_count = tids_to_attach.size();
if (tid_count == 0)
return llvm::make_error<StringError>("No such process",
llvm::inconvertibleErrorCode());
std::vector<::pid_t> tids;
tids.reserve(tid_count);
for (const auto &p : tids_to_attach)
tids.push_back(p.first);
return std::move(tids);
}
llvm::Expected<std::unique_ptr<NativeProcessProtocol>>
NativeProcessLinux::Manager::Attach(
lldb::pid_t pid, NativeProcessProtocol::NativeDelegate &native_delegate) {
Log *log = GetLog(POSIXLog::Process);
LLDB_LOG(log, "pid = {0:x}", pid);
auto tids_or = NativeProcessLinux::Attach(pid);
if (!tids_or)
return tids_or.takeError();
ArrayRef<::pid_t> tids = *tids_or;
llvm::Expected<ArchSpec> arch_or =
NativeRegisterContextLinux::DetermineArchitecture(tids[0]);
if (!arch_or)
return arch_or.takeError();
return std::unique_ptr<NativeProcessLinux>(
new NativeProcessLinux(pid, -1, native_delegate, *arch_or, *this, tids));
}
llvm::Expected<std::vector<::pid_t>> NativeProcessLinux::Attach(::pid_t pid) {
Log *log = GetLog(POSIXLog::Process);
Status status;
Host::TidMap tids_to_attach;
while (Host::FindProcessThreads(pid, tids_to_attach)) {
for (Host::TidMap::iterator it = tids_to_attach.begin();
it != tids_to_attach.end();) {
if (it->second == false) {
lldb::tid_t tid = it->first;
if ((status = PtraceWrapper(PTRACE_ATTACH, tid)).Fail()) {
if (status.GetError() == ESRCH) {
it = tids_to_attach.erase(it);
continue;
}
if (status.GetError() == EPERM) {
return AddPtraceScopeNote(status.ToError());
}
return status.ToError();
}
int wpid =
llvm::sys::RetryAfterSignal(-1, ::waitpid, tid, nullptr, __WALL);
if (wpid < 0) {
if (errno == ESRCH) {
it = tids_to_attach.erase(it);
continue;
}
return llvm::errorCodeToError(
std::error_code(errno, std::generic_category()));
}
if ((status = SetDefaultPtraceOpts(tid)).Fail())
return status.ToError();
LLDB_LOG(log, "adding tid = {0}", tid);
it->second = true;
}
++it;
}
}
size_t tid_count = tids_to_attach.size();
if (tid_count == 0)
return llvm::make_error<StringError>("No such process",
llvm::inconvertibleErrorCode());
std::vector<::pid_t> tids;
tids.reserve(tid_count);
for (const auto &p : tids_to_attach)
tids.push_back(p.first);
return std::move(tids);
}
bool Host::FindProcessThreads(const lldb::pid_t pid, TidMap &tids_to_attach) {
bool tids_changed = false;
static const char procdir[] = "/proc/";
static const char taskdir[] = "/task/";
std::string process_task_dir = procdir + llvm::to_string(pid) + taskdir;
DIR *dirproc = opendir(process_task_dir.c_str());
if (dirproc) {
struct dirent *direntry = nullptr;
while ((direntry = readdir(dirproc)) != nullptr) {
if (direntry->d_type != DT_DIR || !IsDirNumeric(direntry->d_name))
continue;
lldb::tid_t tid = atoi(direntry->d_name);
TidMap::iterator it = tids_to_attach.find(tid);
if (it == tids_to_attach.end()) {
tids_to_attach.insert(TidPair(tid, false));
tids_changed = true;
}
}
closedir(dirproc);
}
return tids_changed;
}
bool Host::FindProcessThreads(const lldb::pid_t pid, TidMap &tids_to_attach) {
bool tids_changed = false;
static const char procdir[] = "/proc/";
static const char taskdir[] = "/task/";
std::string process_task_dir = procdir + llvm::to_string(pid) + taskdir;
DIR *dirproc = opendir(process_task_dir.c_str());
if (dirproc) {
struct dirent *direntry = nullptr;
while ((direntry = readdir(dirproc)) != nullptr) {
if (direntry->d_type != DT_DIR || !IsDirNumeric(direntry->d_name))
continue;
lldb::tid_t tid = atoi(direntry->d_name);
TidMap::iterator it = tids_to_attach.find(tid);
if (it == tids_to_attach.end()) {
tids_to_attach.insert(TidPair(tid, false));
tids_changed = true;
}
}
closedir(dirproc);
}
return tids_changed;
}
Status NativeProcessLinux::Halt() {
Status error;
if (kill(GetID(), SIGSTOP) != 0) {
error.SetErrorToErrno();
}
return error;
}
Status NativeProcessLinux::Halt() {
Status error;
if (kill(GetID(), SIGSTOP) != 0) {
error.SetErrorToErrno();
}
return error;
}
Status NativeProcessLinux::Resume(const ResumeActionList &resume_actions) {
Log *log = GetLog(POSIXLog::Process);
LLDB_LOG(log, "pid {0}", GetID());
NotifyTracersProcessWillResume();
bool software_single_step = !SupportHardwareSingleStepping();
if (software_single_step) {
for (const auto &thread : m_threads) {
assert(thread && "thread list should not contain NULL threads");
const ResumeAction *const action =
resume_actions.GetActionForThread(thread->GetID(), true);
if (action == nullptr)
continue;
if (action->state == eStateStepping) {
Status error = SetupSoftwareSingleStepping(
static_cast<NativeThreadLinux &>(*thread));
if (error.Fail())
return error;
}
}
}
for (const auto &thread : m_threads) {
assert(thread && "thread list should not contain NULL threads");
const ResumeAction *const action =
resume_actions.GetActionForThread(thread->GetID(), true);
if (action == nullptr) {
LLDB_LOG(log, "no action specified for pid {0} tid {1}", GetID(),
thread->GetID());
continue;
}
LLDB_LOG(log, "processing resume action state {0} for pid {1} tid {2}",
action->state, GetID(), thread->GetID());
switch (action->state) {
case eStateRunning:
case eStateStepping: {
const int signo = action->signal;
Status error = ResumeThread(static_cast<NativeThreadLinux &>(*thread),
action->state, signo);
if (error.Fail()) {
return Status("NativeProcessLinux::%s: failed to resume thread "
"for pid %" PRIu64 ", tid %" PRIu64 ", error = %s",
__FUNCTION__, GetID(), thread->GetID(),
error.AsCString());
}
break;
}
case eStateSuspended:
case eStateStopped:
break;
default:
return Status("NativeProcessLinux::%s (): unexpected state %s specified "
"for pid %" PRIu64 ", tid %" PRIu64,
__FUNCTION__, StateAsCString(action->state), GetID(),
thread->GetID());
}
}
return Status();
}
Status NativeProcessLinux::Resume(const ResumeActionList &resume_actions) {
Log *log = GetLog(POSIXLog::Process);
LLDB_LOG(log, "pid {0}", GetID());
NotifyTracersProcessWillResume();
bool software_single_step = !SupportHardwareSingleStepping();
if (software_single_step) {
for (const auto &thread : m_threads) {
assert(thread && "thread list should not contain NULL threads");
const ResumeAction *const action =
resume_actions.GetActionForThread(thread->GetID(), true);
if (action == nullptr)
continue;
if (action->state == eStateStepping) {
Status error = SetupSoftwareSingleStepping(
static_cast<NativeThreadLinux &>(*thread));
if (error.Fail())
return error;
}
}
}
for (const auto &thread : m_threads) {
assert(thread && "thread list should not contain NULL threads");
const ResumeAction *const action =
resume_actions.GetActionForThread(thread->GetID(), true);
if (action == nullptr) {
LLDB_LOG(log, "no action specified for pid {0} tid {1}", GetID(),
thread->GetID());
continue;
}
LLDB_LOG(log, "processing resume action state {0} for pid {1} tid {2}",
action->state, GetID(), thread->GetID());
switch (action->state) {
case eStateRunning:
case eStateStepping: {
const int signo = action->signal;
Status error = ResumeThread(static_cast<NativeThreadLinux &>(*thread),
action->state, signo);
if (error.Fail()) {
return Status("NativeProcessLinux::%s: failed to resume thread "
"for pid %" PRIu64 ", tid %" PRIu64 ", error = %s",
__FUNCTION__, GetID(), thread->GetID(),
error.AsCString());
}
break;
}
case eStateSuspended:
case eStateStopped:
break;
default:
return Status("NativeProcessLinux::%s (): unexpected state %s specified "
"for pid %" PRIu64 ", tid %" PRIu64,
__FUNCTION__, StateAsCString(action->state), GetID(),
thread->GetID());
}
}
return Status();
}
bool NativeProcessLinux::SupportHardwareSingleStepping() const {
if (m_arch.IsMIPS() || m_arch.GetMachine() == llvm::Triple::arm ||
m_arch.GetTriple().isRISCV() || m_arch.GetTriple().isLoongArch())
return false;
return true;
}
bool NativeProcessLinux::SupportHardwareSingleStepping() const {
if (m_arch.IsMIPS() || m_arch.GetMachine() == llvm::Triple::arm ||
m_arch.GetTriple().isRISCV() || m_arch.GetTriple().isLoongArch())
return false;
return true;
}
enum ArchType {
UnknownArch,
arm,
armeb,
aarch64,
aarch64_be,
aarch64_32,
arc,
avr,
bpfel,
bpfeb,
csky,
dxil,
hexagon,
loongarch32,
loongarch64,
m68k,
mips,
mipsel,
mips64,
mips64el,
msp430,
ppc,
ppcle,
ppc64,
ppc64le,
r600,
amdgcn,
riscv32,
riscv64,
sparc,
sparcv9,
sparcel,
systemz,
tce,
tcele,
thumb,
thumbeb,
x86,
x86_64,
xcore,
xtensa,
nvptx,
nvptx64,
le32,
le64,
amdil,
amdil64,
hsail,
hsail64,
spir,
spir64,
spirv32,
spirv64,
kalimba,
shave,
lanai,
wasm32,
wasm64,
renderscript32,
renderscript64,
ve,
LastArchType = ve
};
enum ArchType {
UnknownArch,
arm,
armeb,
aarch64,
aarch64_be,
aarch64_32,
arc,
avr,
bpfel,
bpfeb,
csky,
dxil,
hexagon,
loongarch32,
loongarch64,
m68k,
mips,
mipsel,
mips64,
mips64el,
msp430,
ppc,
ppcle,
ppc64,
ppc64le,
r600,
amdgcn,
riscv32,
riscv64,
sparc,
sparcv9,
sparcel,
systemz,
tce,
tcele,
thumb,
thumbeb,
x86,
x86_64,
xcore,
xtensa,
nvptx,
nvptx64,
le32,
le64,
amdil,
amdil64,
hsail,
hsail64,
spir,
spir64,
spirv32,
spirv64,
kalimba,
shave,
lanai,
wasm32,
wasm64,
renderscript32,
renderscript64,
ve,
LastArchType = ve
};
Status NativeProcessSoftwareSingleStep::SetupSoftwareSingleStepping(
NativeThreadProtocol &thread) {
Status error;
NativeProcessProtocol &process = thread.GetProcess();
NativeRegisterContext ®ister_context = thread.GetRegisterContext();
const ArchSpec &arch = process.GetArchitecture();
std::unique_ptr<EmulateInstruction> emulator_up(
EmulateInstruction::FindPlugin(arch, eInstructionTypePCModifying,
nullptr));
if (emulator_up == nullptr)
return Status("Instruction emulator not found!");
EmulatorBaton baton(process, register_context);
emulator_up->SetBaton(&baton);
emulator_up->SetReadMemCallback(&ReadMemoryCallback);
emulator_up->SetReadRegCallback(&ReadRegisterCallback);
emulator_up->SetWriteMemCallback(&WriteMemoryCallback);
emulator_up->SetWriteRegCallback(&WriteRegisterCallback);
if (!emulator_up->ReadInstruction())
return Status("Read instruction failed!");
bool emulation_result =
emulator_up->EvaluateInstruction(eEmulateInstructionOptionAutoAdvancePC);
const RegisterInfo *reg_info_pc = register_context.GetRegisterInfo(
eRegisterKindGeneric, LLDB_REGNUM_GENERIC_PC);
const RegisterInfo *reg_info_flags = register_context.GetRegisterInfo(
eRegisterKindGeneric, LLDB_REGNUM_GENERIC_FLAGS);
auto pc_it =
baton.m_register_values.find(reg_info_pc->kinds[eRegisterKindDWARF]);
auto flags_it = reg_info_flags == nullptr
? baton.m_register_values.end()
: baton.m_register_values.find(
reg_info_flags->kinds[eRegisterKindDWARF]);
lldb::addr_t next_pc;
lldb::addr_t next_flags;
if (emulation_result) {
assert(pc_it != baton.m_register_values.end() &&
"Emulation was successfull but PC wasn't updated");
next_pc = pc_it->second.GetAsUInt64();
if (flags_it != baton.m_register_values.end())
next_flags = flags_it->second.GetAsUInt64();
else
next_flags = ReadFlags(register_context);
} else if (pc_it == baton.m_register_values.end()) {
next_pc = register_context.GetPC() + emulator_up->GetOpcode().GetByteSize();
next_flags = ReadFlags(register_context);
} else {
return Status("Instruction emulation failed unexpectedly.");
}
int size_hint = 0;
if (arch.GetMachine() == llvm::Triple::arm) {
if (next_flags & 0x20) {
size_hint = 2;
} else {
size_hint = 4;
}
} else if (arch.IsMIPS() || arch.GetTriple().isPPC64() ||
arch.GetTriple().isRISCV() || arch.GetTriple().isLoongArch()) {
size_hint = 4;
}
error = process.SetBreakpoint(next_pc, size_hint, false);
if (error.GetError() == EIO || error.GetError() == EFAULT) {
return Status();
} else if (error.Fail()) {
return error;
}
m_threads_stepping_with_breakpoint.insert({thread.GetID(), next_pc});
return Status();
}
Status NativeProcessSoftwareSingleStep::SetupSoftwareSingleStepping(
NativeThreadProtocol &thread) {
Status error;
NativeProcessProtocol &process = thread.GetProcess();
NativeRegisterContext ®ister_context = thread.GetRegisterContext();
const ArchSpec &arch = process.GetArchitecture();
std::unique_ptr<EmulateInstruction> emulator_up(
EmulateInstruction::FindPlugin(arch, eInstructionTypePCModifying,
nullptr));
if (emulator_up == nullptr)
return Status("Instruction emulator not found!");
EmulatorBaton baton(process, register_context);
emulator_up->SetBaton(&baton);
emulator_up->SetReadMemCallback(&ReadMemoryCallback);
emulator_up->SetReadRegCallback(&ReadRegisterCallback);
emulator_up->SetWriteMemCallback(&WriteMemoryCallback);
emulator_up->SetWriteRegCallback(&WriteRegisterCallback);
if (!emulator_up->ReadInstruction())
return Status("Read instruction failed!");
bool emulation_result =
emulator_up->EvaluateInstruction(eEmulateInstructionOptionAutoAdvancePC);
const RegisterInfo *reg_info_pc = register_context.GetRegisterInfo(
eRegisterKindGeneric, LLDB_REGNUM_GENERIC_PC);
const RegisterInfo *reg_info_flags = register_context.GetRegisterInfo(
eRegisterKindGeneric, LLDB_REGNUM_GENERIC_FLAGS);
auto pc_it =
baton.m_register_values.find(reg_info_pc->kinds[eRegisterKindDWARF]);
auto flags_it = reg_info_flags == nullptr
? baton.m_register_values.end()
: baton.m_register_values.find(
reg_info_flags->kinds[eRegisterKindDWARF]);
lldb::addr_t next_pc;
lldb::addr_t next_flags;
if (emulation_result) {
assert(pc_it != baton.m_register_values.end() &&
"Emulation was successfull but PC wasn't updated");
next_pc = pc_it->second.GetAsUInt64();
if (flags_it != baton.m_register_values.end())
next_flags = flags_it->second.GetAsUInt64();
else
next_flags = ReadFlags(register_context);
} else if (pc_it == baton.m_register_values.end()) {
next_pc = register_context.GetPC() + emulator_up->GetOpcode().GetByteSize();
next_flags = ReadFlags(register_context);
} else {
return Status("Instruction emulation failed unexpectedly.");
}
int size_hint = 0;
if (arch.GetMachine() == llvm::Triple::arm) {
if (next_flags & 0x20) {
size_hint = 2;
} else {
size_hint = 4;
}
} else if (arch.IsMIPS() || arch.GetTriple().isPPC64() ||
arch.GetTriple().isRISCV() || arch.GetTriple().isLoongArch()) {
size_hint = 4;
}
error = process.SetBreakpoint(next_pc, size_hint, false);
if (error.GetError() == EIO || error.GetError() == EFAULT) {
return Status();
} else if (error.Fail()) {
return error;
}
m_threads_stepping_with_breakpoint.insert({thread.GetID(), next_pc});
return Status();
}
static size_t ReadMemoryCallback(EmulateInstruction *instruction, void *baton,
const EmulateInstruction::Context &context,
lldb::addr_t addr, void *dst, size_t length) {
EmulatorBaton *emulator_baton = static_cast<EmulatorBaton *>(baton);
size_t bytes_read;
emulator_baton->m_process.ReadMemory(addr, dst, length, bytes_read);
return bytes_read;
}
static size_t ReadMemoryCallback(EmulateInstruction *instruction, void *baton,
const EmulateInstruction::Context &context,
lldb::addr_t addr, void *dst, size_t length) {
EmulatorBaton *emulator_baton = static_cast<EmulatorBaton *>(baton);
size_t bytes_read;
emulator_baton->m_process.ReadMemory(addr, dst, length, bytes_read);
return bytes_read;
}
Status NativeProcessLinux::ReadMemory(lldb::addr_t addr, void *buf, size_t size,
size_t &bytes_read) {
if (ProcessVmReadvSupported()) {
struct iovec local_iov, remote_iov;
local_iov.iov_base = buf;
local_iov.iov_len = size;
remote_iov.iov_base = reinterpret_cast<void *>(addr);
remote_iov.iov_len = size;
bytes_read = process_vm_readv(GetCurrentThreadID(), &local_iov, 1,
&remote_iov, 1, 0);
const bool success = bytes_read == size;
Log *log = GetLog(POSIXLog::Process);
LLDB_LOG(log,
"using process_vm_readv to read {0} bytes from inferior "
"address {1:x}: {2}",
size, addr, success ? "Success" : llvm::sys::StrError(errno));
if (success)
return Status();
}
unsigned char *dst = static_cast<unsigned char *>(buf);
size_t remainder;
long data;
Log *log = GetLog(POSIXLog::Memory);
LLDB_LOG(log, "addr = {0}, buf = {1}, size = {2}", addr, buf, size);
for (bytes_read = 0; bytes_read < size; bytes_read += remainder) {
Status error = NativeProcessLinux::PtraceWrapper(
PTRACE_PEEKDATA, GetCurrentThreadID(), (void *)addr, nullptr, 0, &data);
if (error.Fail())
return error;
remainder = size - bytes_read;
remainder = remainder > k_ptrace_word_size ? k_ptrace_word_size : remainder;
memcpy(dst, &data, remainder);
LLDB_LOG(log, "[{0:x}]:{1:x}", addr, data);
addr += k_ptrace_word_size;
dst += k_ptrace_word_size;
}
return Status();
}
Status NativeProcessLinux::ReadMemory(lldb::addr_t addr, void *buf, size_t size,
size_t &bytes_read) {
if (ProcessVmReadvSupported()) {
struct iovec local_iov, remote_iov;
local_iov.iov_base = buf;
local_iov.iov_len = size;
remote_iov.iov_base = reinterpret_cast<void *>(addr);
remote_iov.iov_len = size;
bytes_read = process_vm_readv(GetCurrentThreadID(), &local_iov, 1,
&remote_iov, 1, 0);
const bool success = bytes_read == size;
Log *log = GetLog(POSIXLog::Process);
LLDB_LOG(log,
"using process_vm_readv to read {0} bytes from inferior "
"address {1:x}: {2}",
size, addr, success ? "Success" : llvm::sys::StrError(errno));
if (success)
return Status();
}
unsigned char *dst = static_cast<unsigned char *>(buf);
size_t remainder;
long data;
Log *log = GetLog(POSIXLog::Memory);
LLDB_LOG(log, "addr = {0}, buf = {1}, size = {2}", addr, buf, size);
for (bytes_read = 0; bytes_read < size; bytes_read += remainder) {
Status error = NativeProcessLinux::PtraceWrapper(
PTRACE_PEEKDATA, GetCurrentThreadID(), (void *)addr, nullptr, 0, &data);
if (error.Fail())
return error;
remainder = size - bytes_read;
remainder = remainder > k_ptrace_word_size ? k_ptrace_word_size : remainder;
memcpy(dst, &data, remainder);
LLDB_LOG(log, "[{0:x}]:{1:x}", addr, data);
addr += k_ptrace_word_size;
dst += k_ptrace_word_size;
}
return Status();
}
Status NativeProcessLinux::WriteMemory(lldb::addr_t addr, const void *buf,
size_t size, size_t &bytes_written) {
const unsigned char *src = static_cast<const unsigned char *>(buf);
size_t remainder;
Status error;
Log *log = GetLog(POSIXLog::Memory);
LLDB_LOG(log, "addr = {0}, buf = {1}, size = {2}", addr, buf, size);
for (bytes_written = 0; bytes_written < size; bytes_written += remainder) {
remainder = size - bytes_written;
remainder = remainder > k_ptrace_word_size ? k_ptrace_word_size : remainder;
if (remainder == k_ptrace_word_size) {
unsigned long data = 0;
memcpy(&data, src, k_ptrace_word_size);
LLDB_LOG(log, "[{0:x}]:{1:x}", addr, data);
error = NativeProcessLinux::PtraceWrapper(
PTRACE_POKEDATA, GetCurrentThreadID(), (void *)addr, (void *)data);
if (error.Fail())
return error;
} else {
unsigned char buff[8];
size_t bytes_read;
error = ReadMemory(addr, buff, k_ptrace_word_size, bytes_read);
if (error.Fail())
return error;
memcpy(buff, src, remainder);
size_t bytes_written_rec;
error = WriteMemory(addr, buff, k_ptrace_word_size, bytes_written_rec);
if (error.Fail())
return error;
LLDB_LOG(log, "[{0:x}]:{1:x} ({2:x})", addr, *(const unsigned long *)src,
*(unsigned long *)buff);
}
addr += k_ptrace_word_size;
src += k_ptrace_word_size;
}
return error;
}
Status NativeProcessLinux::WriteMemory(lldb::addr_t addr, const void *buf,
size_t size, size_t &bytes_written) {
const unsigned char *src = static_cast<const unsigned char *>(buf);
size_t remainder;
Status error;
Log *log = GetLog(POSIXLog::Memory);
LLDB_LOG(log, "addr = {0}, buf = {1}, size = {2}", addr, buf, size);
for (bytes_written = 0; bytes_written < size; bytes_written += remainder) {
remainder = size - bytes_written;
remainder = remainder > k_ptrace_word_size ? k_ptrace_word_size : remainder;
if (remainder == k_ptrace_word_size) {
unsigned long data = 0;
memcpy(&data, src, k_ptrace_word_size);
LLDB_LOG(log, "[{0:x}]:{1:x}", addr, data);
error = NativeProcessLinux::PtraceWrapper(
PTRACE_POKEDATA, GetCurrentThreadID(), (void *)addr, (void *)data);
if (error.Fail())
return error;
} else {
unsigned char buff[8];
size_t bytes_read;
error = ReadMemory(addr, buff, k_ptrace_word_size, bytes_read);
if (error.Fail())
return error;
memcpy(buff, src, remainder);
size_t bytes_written_rec;
error = WriteMemory(addr, buff, k_ptrace_word_size, bytes_written_rec);
if (error.Fail())
return error;
LLDB_LOG(log, "[{0:x}]:{1:x} ({2:x})", addr, *(const unsigned long *)src,
*(unsigned long *)buff);
}
addr += k_ptrace_word_size;
src += k_ptrace_word_size;
}
return error;
}
static bool ReadRegisterCallback(EmulateInstruction *instruction, void *baton,
const RegisterInfo *reg_info,
RegisterValue ®_value) {
EmulatorBaton *emulator_baton = static_cast<EmulatorBaton *>(baton);
auto it = emulator_baton->m_register_values.find(
reg_info->kinds[eRegisterKindDWARF]);
if (it != emulator_baton->m_register_values.end()) {
reg_value = it->second;
return true;
}
const RegisterInfo *full_reg_info =
emulator_baton->m_reg_context.GetRegisterInfo(
eRegisterKindDWARF, reg_info->kinds[eRegisterKindDWARF]);
Status error =
emulator_baton->m_reg_context.ReadRegister(full_reg_info, reg_value);
if (error.Success())
return true;
return false;
}
static bool ReadRegisterCallback(EmulateInstruction *instruction, void *baton,
const RegisterInfo *reg_info,
RegisterValue ®_value) {
EmulatorBaton *emulator_baton = static_cast<EmulatorBaton *>(baton);
auto it = emulator_baton->m_register_values.find(
reg_info->kinds[eRegisterKindDWARF]);
if (it != emulator_baton->m_register_values.end()) {
reg_value = it->second;
return true;
}
const RegisterInfo *full_reg_info =
emulator_baton->m_reg_context.GetRegisterInfo(
eRegisterKindDWARF, reg_info->kinds[eRegisterKindDWARF]);
Status error =
emulator_baton->m_reg_context.ReadRegister(full_reg_info, reg_value);
if (error.Success())
return true;
return false;
}
Status
NativeRegisterContextLinux_arm64::ReadRegister(const RegisterInfo *reg_info,
RegisterValue ®_value) {
Status error;
if (!reg_info) {
error.SetErrorString("reg_info NULL");
return error;
}
const uint32_t reg = reg_info->kinds[lldb::eRegisterKindLLDB];
if (reg == LLDB_INVALID_REGNUM)
return Status("no lldb regnum for %s",
reg_info && reg_info->name ? reg_info->name : "<unknown register>");
uint8_t *src;
uint32_t offset = LLDB_INVALID_INDEX32;
uint64_t sve_vg;
std::vector<uint8_t> sve_reg_non_live;
if (IsGPR(reg)) {
error = ReadGPR();
if (error.Fail())
return error;
offset = reg_info->byte_offset;
assert(offset < GetGPRSize());
src = (uint8_t *)GetGPRBuffer() + offset;
} else if (IsFPR(reg)) {
if (m_sve_state == SVEState::Disabled) {
error = ReadFPR();
if (error.Fail())
return error;
offset = CalculateFprOffset(reg_info);
assert(offset < GetFPRSize());
src = (uint8_t *)GetFPRBuffer() + offset;
} else {
error = ReadAllSVE();
if (error.Fail())
return error;
uint32_t sve_reg_num = LLDB_INVALID_REGNUM;
if (reg == GetRegisterInfo().GetRegNumFPSR()) {
sve_reg_num = reg;
if (m_sve_state == SVEState::Full)
offset = sve::PTraceFPSROffset(sve::vq_from_vl(m_sve_header.vl));
else if (m_sve_state == SVEState::FPSIMD)
offset = sve::ptrace_fpsimd_offset + (32 * 16);
} else if (reg == GetRegisterInfo().GetRegNumFPCR()) {
sve_reg_num = reg;
if (m_sve_state == SVEState::Full)
offset = sve::PTraceFPCROffset(sve::vq_from_vl(m_sve_header.vl));
else if (m_sve_state == SVEState::FPSIMD)
offset = sve::ptrace_fpsimd_offset + (32 * 16) + 4;
} else {
if (reg_info->value_regs && reg_info->value_regs[0] != LLDB_INVALID_REGNUM)
sve_reg_num = reg_info->value_regs[0];
offset = CalculateSVEOffset(GetRegisterInfoAtIndex(sve_reg_num));
}
assert(offset < GetSVEBufferSize());
src = (uint8_t *)GetSVEBuffer() + offset;
}
} else if (IsTLS(reg)) {
error = ReadTLSTPIDR();
if (error.Fail())
return error;
offset = reg_info->byte_offset - GetRegisterInfo().GetTLSOffset();
assert(offset < GetTLSTPIDRSize());
src = (uint8_t *)GetTLSTPIDR() + offset;
} else if (IsSVE(reg)) {
if (m_sve_state == SVEState::Disabled || m_sve_state == SVEState::Unknown)
return Status("SVE disabled or not supported");
if (GetRegisterInfo().IsSVERegVG(reg)) {
sve_vg = GetSVERegVG();
src = (uint8_t *)&sve_vg;
} else {
error = ReadAllSVE();
if (error.Fail())
return error;
if (m_sve_state == SVEState::FPSIMD) {
sve_reg_non_live.resize(reg_info->byte_size, 0);
src = sve_reg_non_live.data();
if (GetRegisterInfo().IsSVEZReg(reg)) {
offset = CalculateSVEOffset(reg_info);
assert(offset < GetSVEBufferSize());
::memcpy(sve_reg_non_live.data(),
(uint8_t *)GetSVEBuffer() + offset, 16);
}
} else {
offset = CalculateSVEOffset(reg_info);
assert(offset < GetSVEBufferSize());
src = (uint8_t *)GetSVEBuffer() + offset;
}
}
} else if (IsPAuth(reg)) {
error = ReadPAuthMask();
if (error.Fail())
return error;
offset = reg_info->byte_offset - GetRegisterInfo().GetPAuthOffset();
assert(offset < GetPACMaskSize());
src = (uint8_t *)GetPACMask() + offset;
} else if (IsMTE(reg)) {
error = ReadMTEControl();
if (error.Fail())
return error;
offset = reg_info->byte_offset - GetRegisterInfo().GetMTEOffset();
assert(offset < GetMTEControlSize());
src = (uint8_t *)GetMTEControl() + offset;
} else
return Status("failed - register wasn't recognized to be a GPR or an FPR, "
"write strategy unknown");
reg_value.SetFromMemoryData(*reg_info, src, reg_info->byte_size,
eByteOrderLittle, error);
return error;
}
Status
NativeRegisterContextLinux_arm64::ReadRegister(const RegisterInfo *reg_info,
RegisterValue ®_value) {
Status error;
if (!reg_info) {
error.SetErrorString("reg_info NULL");
return error;
}
const uint32_t reg = reg_info->kinds[lldb::eRegisterKindLLDB];
[培训]传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2025-11-5 12:32
被nothing233编辑
,原因: