diff options
Diffstat (limited to 'lldb/tools/debugserver')
84 files changed, 27587 insertions, 0 deletions
diff --git a/lldb/tools/debugserver/debugnub-exports b/lldb/tools/debugserver/debugnub-exports new file mode 100644 index 00000000000..662bf9308a6 --- /dev/null +++ b/lldb/tools/debugserver/debugnub-exports @@ -0,0 +1,2 @@ +_DNB* +__DNB* diff --git a/lldb/tools/debugserver/debugserver.xcodeproj/project.pbxproj b/lldb/tools/debugserver/debugserver.xcodeproj/project.pbxproj new file mode 100644 index 00000000000..14f84acc9f9 --- /dev/null +++ b/lldb/tools/debugserver/debugserver.xcodeproj/project.pbxproj @@ -0,0 +1,604 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 45; + objects = { + +/* Begin PBXBuildFile section */ + 2660D9CE1192280900958FBD /* StringExtractor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2660D9CC1192280900958FBD /* StringExtractor.cpp */; }; + 26CE05A7115C360D0022F371 /* DNBError.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26C637DE0C71334A0024798E /* DNBError.cpp */; }; + 26CE05A8115C36170022F371 /* DNBThreadResumeActions.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 260E7331114BFFE600D1DFB3 /* DNBThreadResumeActions.cpp */; }; + 26CE05A9115C36250022F371 /* debugserver.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26A02918114AB9240029C479 /* debugserver.cpp */; }; + 26CE05AA115C36260022F371 /* RNBContext.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26A68F7E0D104EC800665A9E /* RNBContext.cpp */; }; + 26CE05AB115C36270022F371 /* RNBServices.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EF8878A00D9C797C001831DA /* RNBServices.cpp */; }; + 26CE05AC115C36280022F371 /* RNBSocket.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26A68FB00D1054DA00665A9E /* RNBSocket.cpp */; }; + 26CE05AD115C36280022F371 /* RNBRemote.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26A68FD60D10574500665A9E /* RNBRemote.cpp */; }; + 26CE05AE115C36320022F371 /* dbgnub-mig.defs in Sources */ = {isa = PBXBuildFile; fileRef = 26C637E80C71334A0024798E /* dbgnub-mig.defs */; settings = {ATTRIBUTES = (Client, Server, ); }; }; + 26CE05B0115C36340022F371 /* MachException.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26C637EE0C71334A0024798E /* MachException.cpp */; }; + 26CE05B1115C36350022F371 /* MachProcess.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26C637F00C71334A0024798E /* MachProcess.cpp */; }; + 26CE05B2115C36360022F371 /* MachThread.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26C637F20C71334A0024798E /* MachThread.cpp */; }; + 26CE05B3115C36370022F371 /* MachThreadList.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26C637F40C71334A0024798E /* MachThreadList.cpp */; }; + 26CE05B4115C36380022F371 /* MachVMMemory.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26C637F60C71334A0024798E /* MachVMMemory.cpp */; }; + 26CE05B5115C36380022F371 /* MachVMRegion.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26C637F80C71334A0024798E /* MachVMRegion.cpp */; }; + 26CE05B6115C36390022F371 /* MachTask.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26B67DE10EE9BC30006C8BC0 /* MachTask.cpp */; }; + 26CE05B7115C363B0022F371 /* DNB.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26C637D60C71334A0024798E /* DNB.cpp */; }; + 26CE05B8115C363C0022F371 /* DNBBreakpoint.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26C637D90C71334A0024798E /* DNBBreakpoint.cpp */; }; + 26CE05B9115C363D0022F371 /* DNBDataRef.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26C637DB0C71334A0024798E /* DNBDataRef.cpp */; }; + 26CE05BA115C363E0022F371 /* DNBLog.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26C637E00C71334A0024798E /* DNBLog.cpp */; }; + 26CE05BB115C363F0022F371 /* DNBRegisterInfo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26C637E20C71334A0024798E /* DNBRegisterInfo.cpp */; }; + 26CE05BC115C36420022F371 /* PThreadEvent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26C637FE0C71334A0024798E /* PThreadEvent.cpp */; }; + 26CE05BD115C36430022F371 /* PThreadMutex.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2672DBEE0EEF446700E92059 /* PThreadMutex.cpp */; }; + 26CE05BE115C36440022F371 /* SysSignal.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26C638010C71334A0024798E /* SysSignal.cpp */; }; + 26CE05BF115C364D0022F371 /* DNBArchImplX86_64.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26CF99A21142EB7400011AAB /* DNBArchImplX86_64.cpp */; }; + 26CE05C0115C364F0022F371 /* DNBArchImplI386.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26C637EA0C71334A0024798E /* DNBArchImplI386.cpp */; }; + 26CE05C1115C36510022F371 /* DNBArchImpl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2675D4220CCEB705000F49AF /* DNBArchImpl.cpp */; }; + 26CE05C2115C36550022F371 /* DNBArchImpl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26C637FB0C71334A0024798E /* DNBArchImpl.cpp */; }; + 26CE05C3115C36580022F371 /* CFString.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2695DD9B0D3EC160007E4CA2 /* CFString.cpp */; }; + 26CE05C4115C36590022F371 /* CFData.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2695DE2E0D3EE55B007E4CA2 /* CFData.cpp */; }; + 26CE05C5115C36590022F371 /* CFBundle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2695DD910D3EBFF6007E4CA2 /* CFBundle.cpp */; }; + 26CE05CF115C36F70022F371 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 26ACA3340D3E956300A2120B /* CoreFoundation.framework */; }; + 26CE05F1115C387C0022F371 /* PseudoTerminal.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AF67ABFF0D34604D0022D128 /* PseudoTerminal.cpp */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 260828DE0CBAF7F400F95054 /* DNBRuntimeAction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DNBRuntimeAction.h; sourceTree = "<group>"; }; + 260E7331114BFFE600D1DFB3 /* DNBThreadResumeActions.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DNBThreadResumeActions.cpp; sourceTree = "<group>"; }; + 260E7332114BFFE600D1DFB3 /* DNBThreadResumeActions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DNBThreadResumeActions.h; sourceTree = "<group>"; }; + 260FC7320E5B290400043FC9 /* debugnub-exports */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "debugnub-exports"; sourceTree = SOURCE_ROOT; }; + 26242C390DDBD33C0054A4CC /* debugserver-entitlements.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "debugserver-entitlements.plist"; sourceTree = "<group>"; }; + 26593A060D4931CC001C9FE3 /* ChangeLog */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = ChangeLog; sourceTree = "<group>"; }; + 2660D9CC1192280900958FBD /* StringExtractor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StringExtractor.cpp; path = ../../source/Utility/StringExtractor.cpp; sourceTree = SOURCE_ROOT; }; + 2660D9CD1192280900958FBD /* StringExtractor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = StringExtractor.h; path = ../../source/Utility/StringExtractor.h; sourceTree = SOURCE_ROOT; }; + 2672DBEE0EEF446700E92059 /* PThreadMutex.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PThreadMutex.cpp; sourceTree = "<group>"; }; + 2675D4220CCEB705000F49AF /* DNBArchImpl.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = DNBArchImpl.cpp; path = arm/DNBArchImpl.cpp; sourceTree = "<group>"; }; + 2675D4230CCEB705000F49AF /* DNBArchImpl.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = DNBArchImpl.h; path = arm/DNBArchImpl.h; sourceTree = "<group>"; }; + 2695DD910D3EBFF6007E4CA2 /* CFBundle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CFBundle.cpp; sourceTree = "<group>"; }; + 2695DD920D3EBFF6007E4CA2 /* CFBundle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CFBundle.h; sourceTree = "<group>"; }; + 2695DD9A0D3EC160007E4CA2 /* CFString.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CFString.h; sourceTree = "<group>"; }; + 2695DD9B0D3EC160007E4CA2 /* CFString.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CFString.cpp; sourceTree = "<group>"; }; + 2695DE2D0D3EE55B007E4CA2 /* CFData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CFData.h; sourceTree = "<group>"; }; + 2695DE2E0D3EE55B007E4CA2 /* CFData.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CFData.cpp; sourceTree = "<group>"; }; + 269DE5C50CB5B723008989F0 /* ProfileObjectiveC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ProfileObjectiveC.h; sourceTree = "<group>"; }; + 269DE5C60CB5B723008989F0 /* ProfileObjectiveC.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ProfileObjectiveC.cpp; sourceTree = "<group>"; }; + 26A02918114AB9240029C479 /* debugserver.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = debugserver.cpp; sourceTree = "<group>"; }; + 26A4BAED0D498B7D00A9BEAB /* com.apple.debugserver.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = com.apple.debugserver.plist; sourceTree = "<group>"; }; + 26A68F7D0D104EC800665A9E /* RNBContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNBContext.h; sourceTree = "<group>"; }; + 26A68F7E0D104EC800665A9E /* RNBContext.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RNBContext.cpp; sourceTree = "<group>"; }; + 26A68FAF0D1054DA00665A9E /* RNBSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNBSocket.h; sourceTree = "<group>"; }; + 26A68FB00D1054DA00665A9E /* RNBSocket.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RNBSocket.cpp; sourceTree = "<group>"; }; + 26A68FD50D10574500665A9E /* RNBRemote.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNBRemote.h; sourceTree = "<group>"; }; + 26A68FD60D10574500665A9E /* RNBRemote.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RNBRemote.cpp; sourceTree = "<group>"; }; + 26A8FE1E0D11A77B00203048 /* DNBTimer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DNBTimer.h; sourceTree = "<group>"; }; + 26A901EA0EA3F46B00F7C71E /* FunctionProfiler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FunctionProfiler.cpp; sourceTree = "<group>"; }; + 26A901EB0EA3F46B00F7C71E /* FunctionProfiler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FunctionProfiler.h; sourceTree = "<group>"; }; + 26ACA3340D3E956300A2120B /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; + 26B67DE00EE9BC30006C8BC0 /* MachTask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MachTask.h; sourceTree = "<group>"; }; + 26B67DE10EE9BC30006C8BC0 /* MachTask.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MachTask.cpp; sourceTree = "<group>"; }; + 26C636AD0C71303A0024798E /* dbgnub-config.pl */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.perl; path = "dbgnub-config.pl"; sourceTree = "<group>"; }; + 26C637D60C71334A0024798E /* DNB.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = DNB.cpp; sourceTree = "<group>"; }; + 26C637D70C71334A0024798E /* DNB.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = DNB.h; sourceTree = "<group>"; }; + 26C637D80C71334A0024798E /* DNBArch.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = DNBArch.h; sourceTree = "<group>"; }; + 26C637D90C71334A0024798E /* DNBBreakpoint.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = DNBBreakpoint.cpp; sourceTree = "<group>"; }; + 26C637DA0C71334A0024798E /* DNBBreakpoint.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = DNBBreakpoint.h; sourceTree = "<group>"; }; + 26C637DB0C71334A0024798E /* DNBDataRef.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = DNBDataRef.cpp; sourceTree = "<group>"; }; + 26C637DC0C71334A0024798E /* DNBDataRef.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = DNBDataRef.h; sourceTree = "<group>"; }; + 26C637DD0C71334A0024798E /* DNBDefs.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = DNBDefs.h; sourceTree = "<group>"; }; + 26C637DE0C71334A0024798E /* DNBError.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = DNBError.cpp; sourceTree = "<group>"; }; + 26C637DF0C71334A0024798E /* DNBError.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = DNBError.h; sourceTree = "<group>"; }; + 26C637E00C71334A0024798E /* DNBLog.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = DNBLog.cpp; sourceTree = "<group>"; }; + 26C637E10C71334A0024798E /* DNBLog.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = DNBLog.h; sourceTree = "<group>"; }; + 26C637E20C71334A0024798E /* DNBRegisterInfo.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = DNBRegisterInfo.cpp; sourceTree = "<group>"; }; + 26C637E30C71334A0024798E /* DNBRegisterInfo.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = DNBRegisterInfo.h; sourceTree = "<group>"; }; + 26C637E70C71334A0024798E /* CFUtils.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = CFUtils.h; sourceTree = "<group>"; }; + 26C637E80C71334A0024798E /* dbgnub-mig.defs */ = {isa = PBXFileReference; explicitFileType = sourcecode.mig; fileEncoding = 30; path = "dbgnub-mig.defs"; sourceTree = "<group>"; }; + 26C637EA0C71334A0024798E /* DNBArchImplI386.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = DNBArchImplI386.cpp; sourceTree = "<group>"; }; + 26C637EB0C71334A0024798E /* DNBArchImplI386.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = DNBArchImplI386.h; sourceTree = "<group>"; }; + 26C637EC0C71334A0024798E /* MachDYLD.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = MachDYLD.cpp; sourceTree = "<group>"; }; + 26C637ED0C71334A0024798E /* MachDYLD.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = MachDYLD.h; sourceTree = "<group>"; }; + 26C637EE0C71334A0024798E /* MachException.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = MachException.cpp; sourceTree = "<group>"; }; + 26C637EF0C71334A0024798E /* MachException.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = MachException.h; sourceTree = "<group>"; }; + 26C637F00C71334A0024798E /* MachProcess.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = MachProcess.cpp; sourceTree = "<group>"; }; + 26C637F10C71334A0024798E /* MachProcess.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = MachProcess.h; sourceTree = "<group>"; }; + 26C637F20C71334A0024798E /* MachThread.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = MachThread.cpp; sourceTree = "<group>"; }; + 26C637F30C71334A0024798E /* MachThread.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = MachThread.h; sourceTree = "<group>"; }; + 26C637F40C71334A0024798E /* MachThreadList.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = MachThreadList.cpp; sourceTree = "<group>"; }; + 26C637F50C71334A0024798E /* MachThreadList.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = MachThreadList.h; sourceTree = "<group>"; }; + 26C637F60C71334A0024798E /* MachVMMemory.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = MachVMMemory.cpp; sourceTree = "<group>"; }; + 26C637F70C71334A0024798E /* MachVMMemory.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = MachVMMemory.h; sourceTree = "<group>"; }; + 26C637F80C71334A0024798E /* MachVMRegion.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = MachVMRegion.cpp; sourceTree = "<group>"; }; + 26C637F90C71334A0024798E /* MachVMRegion.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = MachVMRegion.h; sourceTree = "<group>"; }; + 26C637FB0C71334A0024798E /* DNBArchImpl.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = DNBArchImpl.cpp; sourceTree = "<group>"; }; + 26C637FC0C71334A0024798E /* DNBArchImpl.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = DNBArchImpl.h; sourceTree = "<group>"; }; + 26C637FD0C71334A0024798E /* PThreadCondition.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = PThreadCondition.h; sourceTree = "<group>"; }; + 26C637FE0C71334A0024798E /* PThreadEvent.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = PThreadEvent.cpp; sourceTree = "<group>"; }; + 26C637FF0C71334A0024798E /* PThreadEvent.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = PThreadEvent.h; sourceTree = "<group>"; }; + 26C638000C71334A0024798E /* PThreadMutex.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = PThreadMutex.h; sourceTree = "<group>"; }; + 26C638010C71334A0024798E /* SysSignal.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = SysSignal.cpp; sourceTree = "<group>"; }; + 26C638020C71334A0024798E /* SysSignal.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = SysSignal.h; sourceTree = "<group>"; }; + 26C638050C71334A0024798E /* TTYState.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = TTYState.cpp; sourceTree = "<group>"; }; + 26C638060C71334A0024798E /* TTYState.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = TTYState.h; sourceTree = "<group>"; }; + 26CE0594115C31C20022F371 /* debugserver */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = debugserver; sourceTree = BUILT_PRODUCTS_DIR; }; + 26CF99A21142EB7400011AAB /* DNBArchImplX86_64.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DNBArchImplX86_64.cpp; sourceTree = "<group>"; }; + 26CF99A31142EB7400011AAB /* DNBArchImplX86_64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DNBArchImplX86_64.h; sourceTree = "<group>"; }; + 26E6B9DA0D1329010037ECDD /* RNBDefs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNBDefs.h; sourceTree = "<group>"; }; + AF67ABFF0D34604D0022D128 /* PseudoTerminal.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PseudoTerminal.cpp; sourceTree = "<group>"; }; + AF67AC000D34604D0022D128 /* PseudoTerminal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PseudoTerminal.h; sourceTree = "<group>"; }; + EF88788B0D9C7558001831DA /* com.apple.debugserver.applist.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = com.apple.debugserver.applist.plist; sourceTree = "<group>"; }; + EF88789F0D9C797C001831DA /* RNBServices.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNBServices.h; sourceTree = "<group>"; }; + EF8878A00D9C797C001831DA /* RNBServices.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RNBServices.cpp; sourceTree = "<group>"; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 26CE0592115C31C20022F371 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 26CE05CF115C36F70022F371 /* CoreFoundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 08FB7794FE84155DC02AAC07 /* dbgnub */ = { + isa = PBXGroup; + children = ( + 26ACA3330D3E94F200A2120B /* Framework */, + 26C637D50C71334A0024798E /* source */, + 26C636AC0C71303A0024798E /* scripts */, + 1AB674ADFE9D54B511CA2CBB /* Products */, + ); + name = dbgnub; + sourceTree = "<group>"; + }; + 1AB674ADFE9D54B511CA2CBB /* Products */ = { + isa = PBXGroup; + children = ( + 26CE0594115C31C20022F371 /* debugserver */, + ); + name = Products; + sourceTree = "<group>"; + }; + 2675D41C0CCEB6CF000F49AF /* arm */ = { + isa = PBXGroup; + children = ( + 2675D4220CCEB705000F49AF /* DNBArchImpl.cpp */, + 2675D4230CCEB705000F49AF /* DNBArchImpl.h */, + ); + name = arm; + sourceTree = "<group>"; + }; + 26A028FE114AB6A60029C479 /* Resources */ = { + isa = PBXGroup; + children = ( + 260FC7320E5B290400043FC9 /* debugnub-exports */, + 26242C390DDBD33C0054A4CC /* debugserver-entitlements.plist */, + 26A4BAED0D498B7D00A9BEAB /* com.apple.debugserver.plist */, + EF88788B0D9C7558001831DA /* com.apple.debugserver.applist.plist */, + ); + name = Resources; + sourceTree = "<group>"; + }; + 26A028FF114AB6BB0029C479 /* libdebugnub */ = { + isa = PBXGroup; + children = ( + 26C637E60C71334A0024798E /* MacOSX */, + 260828DE0CBAF7F400F95054 /* DNBRuntimeAction.h */, + 26A8FE1E0D11A77B00203048 /* DNBTimer.h */, + 26C637D70C71334A0024798E /* DNB.h */, + 26C637D60C71334A0024798E /* DNB.cpp */, + 26C637D80C71334A0024798E /* DNBArch.h */, + 26C637DA0C71334A0024798E /* DNBBreakpoint.h */, + 26C637D90C71334A0024798E /* DNBBreakpoint.cpp */, + 26C637DC0C71334A0024798E /* DNBDataRef.h */, + 26C637DB0C71334A0024798E /* DNBDataRef.cpp */, + 26C637DD0C71334A0024798E /* DNBDefs.h */, + 26C637DF0C71334A0024798E /* DNBError.h */, + 26C637DE0C71334A0024798E /* DNBError.cpp */, + 26C637E10C71334A0024798E /* DNBLog.h */, + 26C637E00C71334A0024798E /* DNBLog.cpp */, + 26C637E30C71334A0024798E /* DNBRegisterInfo.h */, + 26C637E20C71334A0024798E /* DNBRegisterInfo.cpp */, + 260E7332114BFFE600D1DFB3 /* DNBThreadResumeActions.h */, + 260E7331114BFFE600D1DFB3 /* DNBThreadResumeActions.cpp */, + AF67AC000D34604D0022D128 /* PseudoTerminal.h */, + AF67ABFF0D34604D0022D128 /* PseudoTerminal.cpp */, + 26C637FD0C71334A0024798E /* PThreadCondition.h */, + 26C637FF0C71334A0024798E /* PThreadEvent.h */, + 26C637FE0C71334A0024798E /* PThreadEvent.cpp */, + 26C638000C71334A0024798E /* PThreadMutex.h */, + 2672DBEE0EEF446700E92059 /* PThreadMutex.cpp */, + 26C638020C71334A0024798E /* SysSignal.h */, + 26C638010C71334A0024798E /* SysSignal.cpp */, + 26C638060C71334A0024798E /* TTYState.h */, + 26C638050C71334A0024798E /* TTYState.cpp */, + ); + name = libdebugnub; + sourceTree = "<group>"; + }; + 26A02900114AB6DB0029C479 /* Utility */ = { + isa = PBXGroup; + children = ( + 26A901EB0EA3F46B00F7C71E /* FunctionProfiler.h */, + 26A901EA0EA3F46B00F7C71E /* FunctionProfiler.cpp */, + 269DE5C50CB5B723008989F0 /* ProfileObjectiveC.h */, + 269DE5C60CB5B723008989F0 /* ProfileObjectiveC.cpp */, + ); + name = Utility; + sourceTree = "<group>"; + }; + 26ACA3330D3E94F200A2120B /* Framework */ = { + isa = PBXGroup; + children = ( + 26ACA3340D3E956300A2120B /* CoreFoundation.framework */, + ); + name = Framework; + sourceTree = "<group>"; + }; + 26C636AC0C71303A0024798E /* scripts */ = { + isa = PBXGroup; + children = ( + 26C636AD0C71303A0024798E /* dbgnub-config.pl */, + ); + path = scripts; + sourceTree = "<group>"; + }; + 26C637D50C71334A0024798E /* source */ = { + isa = PBXGroup; + children = ( + 26593A060D4931CC001C9FE3 /* ChangeLog */, + 26DEFD6C0D104C23008A5A07 /* debugserver */, + 26A028FF114AB6BB0029C479 /* libdebugnub */, + 26A02900114AB6DB0029C479 /* Utility */, + ); + indentWidth = 4; + path = source; + sourceTree = "<group>"; + tabWidth = 4; + usesTabs = 0; + }; + 26C637E60C71334A0024798E /* MacOSX */ = { + isa = PBXGroup; + children = ( + 2695DD920D3EBFF6007E4CA2 /* CFBundle.h */, + 2695DD910D3EBFF6007E4CA2 /* CFBundle.cpp */, + 2695DE2D0D3EE55B007E4CA2 /* CFData.h */, + 2695DE2E0D3EE55B007E4CA2 /* CFData.cpp */, + 2695DD9A0D3EC160007E4CA2 /* CFString.h */, + 2695DD9B0D3EC160007E4CA2 /* CFString.cpp */, + 26C637E70C71334A0024798E /* CFUtils.h */, + 2675D41C0CCEB6CF000F49AF /* arm */, + 26C637E90C71334A0024798E /* i386 */, + 26C637FA0C71334A0024798E /* ppc */, + 26CF99A11142EB7400011AAB /* x86_64 */, + 26C637E80C71334A0024798E /* dbgnub-mig.defs */, + 26C637ED0C71334A0024798E /* MachDYLD.h */, + 26C637EC0C71334A0024798E /* MachDYLD.cpp */, + 26C637EF0C71334A0024798E /* MachException.h */, + 26C637EE0C71334A0024798E /* MachException.cpp */, + 26C637F10C71334A0024798E /* MachProcess.h */, + 26C637F00C71334A0024798E /* MachProcess.cpp */, + 26C637F30C71334A0024798E /* MachThread.h */, + 26C637F20C71334A0024798E /* MachThread.cpp */, + 26C637F50C71334A0024798E /* MachThreadList.h */, + 26C637F40C71334A0024798E /* MachThreadList.cpp */, + 26C637F70C71334A0024798E /* MachVMMemory.h */, + 26C637F60C71334A0024798E /* MachVMMemory.cpp */, + 26C637F90C71334A0024798E /* MachVMRegion.h */, + 26C637F80C71334A0024798E /* MachVMRegion.cpp */, + 26B67DE00EE9BC30006C8BC0 /* MachTask.h */, + 26B67DE10EE9BC30006C8BC0 /* MachTask.cpp */, + ); + path = MacOSX; + sourceTree = "<group>"; + }; + 26C637E90C71334A0024798E /* i386 */ = { + isa = PBXGroup; + children = ( + 26C637EA0C71334A0024798E /* DNBArchImplI386.cpp */, + 26C637EB0C71334A0024798E /* DNBArchImplI386.h */, + ); + path = i386; + sourceTree = "<group>"; + }; + 26C637FA0C71334A0024798E /* ppc */ = { + isa = PBXGroup; + children = ( + 26C637FB0C71334A0024798E /* DNBArchImpl.cpp */, + 26C637FC0C71334A0024798E /* DNBArchImpl.h */, + ); + path = ppc; + sourceTree = "<group>"; + }; + 26CF99A11142EB7400011AAB /* x86_64 */ = { + isa = PBXGroup; + children = ( + 26CF99A21142EB7400011AAB /* DNBArchImplX86_64.cpp */, + 26CF99A31142EB7400011AAB /* DNBArchImplX86_64.h */, + ); + path = x86_64; + sourceTree = "<group>"; + }; + 26DEFD6C0D104C23008A5A07 /* debugserver */ = { + isa = PBXGroup; + children = ( + 26A02918114AB9240029C479 /* debugserver.cpp */, + 26A028FE114AB6A60029C479 /* Resources */, + 26A68F7D0D104EC800665A9E /* RNBContext.h */, + 26A68F7E0D104EC800665A9E /* RNBContext.cpp */, + EF88789F0D9C797C001831DA /* RNBServices.h */, + EF8878A00D9C797C001831DA /* RNBServices.cpp */, + 26A68FAF0D1054DA00665A9E /* RNBSocket.h */, + 26A68FB00D1054DA00665A9E /* RNBSocket.cpp */, + 26A68FD50D10574500665A9E /* RNBRemote.h */, + 26A68FD60D10574500665A9E /* RNBRemote.cpp */, + 26E6B9DA0D1329010037ECDD /* RNBDefs.h */, + 2660D9CD1192280900958FBD /* StringExtractor.h */, + 2660D9CC1192280900958FBD /* StringExtractor.cpp */, + ); + name = debugserver; + sourceTree = "<group>"; + usesTabs = 0; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 26CE0593115C31C20022F371 /* debugserver */ = { + isa = PBXNativeTarget; + buildConfigurationList = 26CE05A4115C31ED0022F371 /* Build configuration list for PBXNativeTarget "debugserver" */; + buildPhases = ( + 26CE05C7115C36870022F371 /* ShellScript */, + 26CE0591115C31C20022F371 /* Sources */, + 26CE0592115C31C20022F371 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = debugserver; + productName = "lldb-debugserver"; + productReference = 26CE0594115C31C20022F371 /* debugserver */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 08FB7793FE84155DC02AAC07 /* Project object */ = { + isa = PBXProject; + buildConfigurationList = 1DEB914E08733D8E0010E9CD /* Build configuration list for PBXProject "debugserver" */; + compatibilityVersion = "Xcode 3.1"; + hasScannedForEncodings = 1; + mainGroup = 08FB7794FE84155DC02AAC07 /* dbgnub */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 26CE0593115C31C20022F371 /* debugserver */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXShellScriptBuildPhase section */ + 26CE05C7115C36870022F371 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "$(SRCROOT)/scripts/dbgnub-config.pl", + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/DNBConfig.h", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "perl -x scripts/dbgnub-config.pl\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 26CE0591115C31C20022F371 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 26CE05A7115C360D0022F371 /* DNBError.cpp in Sources */, + 26CE05A8115C36170022F371 /* DNBThreadResumeActions.cpp in Sources */, + 26CE05A9115C36250022F371 /* debugserver.cpp in Sources */, + 26CE05AA115C36260022F371 /* RNBContext.cpp in Sources */, + 26CE05AB115C36270022F371 /* RNBServices.cpp in Sources */, + 26CE05AC115C36280022F371 /* RNBSocket.cpp in Sources */, + 26CE05AD115C36280022F371 /* RNBRemote.cpp in Sources */, + 26CE05AE115C36320022F371 /* dbgnub-mig.defs in Sources */, + 26CE05B0115C36340022F371 /* MachException.cpp in Sources */, + 26CE05B1115C36350022F371 /* MachProcess.cpp in Sources */, + 26CE05B2115C36360022F371 /* MachThread.cpp in Sources */, + 26CE05B3115C36370022F371 /* MachThreadList.cpp in Sources */, + 26CE05B4115C36380022F371 /* MachVMMemory.cpp in Sources */, + 26CE05B5115C36380022F371 /* MachVMRegion.cpp in Sources */, + 26CE05B6115C36390022F371 /* MachTask.cpp in Sources */, + 26CE05B7115C363B0022F371 /* DNB.cpp in Sources */, + 26CE05B8115C363C0022F371 /* DNBBreakpoint.cpp in Sources */, + 26CE05B9115C363D0022F371 /* DNBDataRef.cpp in Sources */, + 26CE05BA115C363E0022F371 /* DNBLog.cpp in Sources */, + 26CE05BB115C363F0022F371 /* DNBRegisterInfo.cpp in Sources */, + 26CE05BC115C36420022F371 /* PThreadEvent.cpp in Sources */, + 26CE05BD115C36430022F371 /* PThreadMutex.cpp in Sources */, + 26CE05BE115C36440022F371 /* SysSignal.cpp in Sources */, + 26CE05BF115C364D0022F371 /* DNBArchImplX86_64.cpp in Sources */, + 26CE05C0115C364F0022F371 /* DNBArchImplI386.cpp in Sources */, + 26CE05C1115C36510022F371 /* DNBArchImpl.cpp in Sources */, + 26CE05C2115C36550022F371 /* DNBArchImpl.cpp in Sources */, + 26CE05C3115C36580022F371 /* CFString.cpp in Sources */, + 26CE05C4115C36590022F371 /* CFData.cpp in Sources */, + 26CE05C5115C36590022F371 /* CFBundle.cpp in Sources */, + 26CE05F1115C387C0022F371 /* PseudoTerminal.cpp in Sources */, + 2660D9CE1192280900958FBD /* StringExtractor.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 1DEB914F08733D8E0010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 112; + FRAMEWORK_SEARCH_PATHS = $SDKROOT/System/Library/PrivateFrameworks; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + STRIP_INSTALLED_PRODUCT = NO; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_BUILDER = "$(USER)"; + }; + name = Debug; + }; + 1DEB915008733D8E0010E9CD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; + CURRENT_PROJECT_VERSION = 112; + DEAD_CODE_STRIPPING = YES; + FRAMEWORK_SEARCH_PATHS = $SDKROOT/System/Library/PrivateFrameworks; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + STRIPFLAGS = "-x"; + STRIP_STYLE = debugging; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_BUILDER = "$(USER)"; + }; + name = Release; + }; + 262419A11198A93E00067686 /* BuildAndIntegration */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; + CURRENT_PROJECT_VERSION = 112; + DEAD_CODE_STRIPPING = YES; + FRAMEWORK_SEARCH_PATHS = $SDKROOT/System/Library/PrivateFrameworks; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + STRIPFLAGS = "-x"; + STRIP_STYLE = debugging; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_BUILDER = "$(USER)"; + }; + name = BuildAndIntegration; + }; + 262419A21198A93E00067686 /* BuildAndIntegration */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = ( + x86_64, + i386, + ); + COPY_PHASE_STRIP = YES; + CURRENT_PROJECT_VERSION = 112; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_PREPROCESSOR_DEFINITIONS = LLDB_DEBUGSERVER; + INSTALL_PATH = /Developer/usr/bin; + LLDB_DEBUGSERVER = 1; + OTHER_LDFLAGS = ( + "-sectcreate", + __TEXT, + __info_plist, + "$(PROJECT_DIR)/resources/lldb-debugserver-Info.plist", + ); + OTHER_MIGFLAGS = "-I$(DERIVED_FILE_DIR)"; + PREBINDING = NO; + PRODUCT_NAME = debugserver; + STRIP_INSTALLED_PRODUCT = YES; + USER_HEADER_SEARCH_PATHS = "./source $(DERIVED_SOURCES_DIR)"; + ZERO_LINK = NO; + }; + name = BuildAndIntegration; + }; + 26CE0596115C31C30022F371 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = ( + x86_64, + i386, + ); + CODE_SIGN_IDENTITY = lldb_codesign; + COPY_PHASE_STRIP = YES; + CURRENT_PROJECT_VERSION = 112; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = LLDB_DEBUGSERVER; + INSTALL_PATH = /Developer/usr/bin; + LLDB_DEBUGSERVER = 1; + OTHER_LDFLAGS = ( + "-sectcreate", + __TEXT, + __info_plist, + "$(PROJECT_DIR)/resources/lldb-debugserver-Info.plist", + ); + OTHER_MIGFLAGS = "-I$(DERIVED_FILE_DIR)"; + PREBINDING = NO; + PRODUCT_NAME = debugserver; + USER_HEADER_SEARCH_PATHS = "./source $(DERIVED_SOURCES_DIR)"; + ZERO_LINK = NO; + }; + name = Debug; + }; + 26CE0597115C31C30022F371 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = ( + x86_64, + i386, + ); + CODE_SIGN_IDENTITY = lldb_codesign; + COPY_PHASE_STRIP = YES; + CURRENT_PROJECT_VERSION = 112; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_PREPROCESSOR_DEFINITIONS = LLDB_DEBUGSERVER; + INSTALL_PATH = /Developer/usr/bin; + LLDB_DEBUGSERVER = 1; + OTHER_LDFLAGS = ( + "-sectcreate", + __TEXT, + __info_plist, + "$(PROJECT_DIR)/resources/lldb-debugserver-Info.plist", + ); + OTHER_MIGFLAGS = "-I$(DERIVED_FILE_DIR)"; + PREBINDING = NO; + PRODUCT_NAME = debugserver; + USER_HEADER_SEARCH_PATHS = "./source $(DERIVED_SOURCES_DIR)"; + ZERO_LINK = NO; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1DEB914E08733D8E0010E9CD /* Build configuration list for PBXProject "debugserver" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB914F08733D8E0010E9CD /* Debug */, + 1DEB915008733D8E0010E9CD /* Release */, + 262419A11198A93E00067686 /* BuildAndIntegration */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = BuildAndIntegration; + }; + 26CE05A4115C31ED0022F371 /* Build configuration list for PBXNativeTarget "debugserver" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 26CE0596115C31C30022F371 /* Debug */, + 26CE0597115C31C30022F371 /* Release */, + 262419A21198A93E00067686 /* BuildAndIntegration */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = BuildAndIntegration; + }; +/* End XCConfigurationList section */ + }; + rootObject = 08FB7793FE84155DC02AAC07 /* Project object */; +} diff --git a/lldb/tools/debugserver/resources/lldb-debugserver-Info.plist b/lldb/tools/debugserver/resources/lldb-debugserver-Info.plist new file mode 100644 index 00000000000..ae9b6b28b11 --- /dev/null +++ b/lldb/tools/debugserver/resources/lldb-debugserver-Info.plist @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleIdentifier</key> + <string>com.apple.${PRODUCT_NAME:rfc1034identifier}</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>${EXECUTABLE_NAME}</string> + <key>CFBundleVersion</key> + <string>2</string> + <key>SecTaskAccess</key> + <array> + <string>allowed</string> + <string>debug</string> + </array> +</dict> +</plist> diff --git a/lldb/tools/debugserver/resources/lldb-debugserver-entitlements.plist b/lldb/tools/debugserver/resources/lldb-debugserver-entitlements.plist new file mode 100644 index 00000000000..42cfb9cdc0e --- /dev/null +++ b/lldb/tools/debugserver/resources/lldb-debugserver-entitlements.plist @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>get-task-allow</key> + <true/> +</dict> +</plist> diff --git a/lldb/tools/debugserver/scripts/dbgnub-config.pl b/lldb/tools/debugserver/scripts/dbgnub-config.pl new file mode 100644 index 00000000000..c953cc2e1c5 --- /dev/null +++ b/lldb/tools/debugserver/scripts/dbgnub-config.pl @@ -0,0 +1,71 @@ +#!/usr/bin/perl + +use strict; +my $config_file = "$ENV{SCRIPT_OUTPUT_FILE_0}"; + +# Define the tests we need to run during this configuration +my @config_tests = ( + { + NAME => "HAVE_64_BIT_MACH_EXCEPTIONS", + TEST => "-e '$ENV{SDKROOT}/usr/include/mach/mach_exc.defs'", + COMMENT => "// Defined if we can use 64 bit mach exceptions", + FAIL => "#undef HAVE_64_BIT_MACH_EXCEPTIONS\ +#define mach_exception_data_t exception_data_t\ +#define mach_exception_data_type_t exception_data_type_t\ +#define mach_exc_server exc_server\ +#define MACH_EXCEPTION_CODES 0\n", + SUCCESS => "#define HAVE_64_BIT_MACH_EXCEPTIONS 1\n", + } +); + +#---------------------------------------------------------------------- +# Open the config file +#---------------------------------------------------------------------- +open(CONFIG, "> $config_file") || die "Couldn't open '$config_file' for writing: $!\n"; +print CONFIG "/*" . "-" x 72 . "\n"; +print CONFIG "// This file is auto generated by a dbgnub-config.pl, do not edit by hand!\n"; +print CONFIG "//" . "-" x 72 . "\n"; +print CONFIG "// COMMAND LINE\n"; +print CONFIG "// " . join(' ', @ARGV) . "\n"; +print CONFIG "//" . "-" x 72 . "\n"; +print CONFIG "// ENVIRONMENT\n"; +my $key; +my $val; +while (($key, $val) = each %ENV) +{ + printf CONFIG "// %s = %s\n", $key, $val; +} +print CONFIG "//" . "-" x 72 . "\n"; +print CONFIG "// SETTINGS\n"; +print CONFIG "// config_file: '$config_file'\n"; +print CONFIG "//" . "-" x 72 . "\n"; +print CONFIG "*/\n\n"; +print CONFIG "#ifndef __DBGNUB_CONFIG__\n"; +print CONFIG "#define __DBGNUB_CONFIG__\n"; + + +#---------------------------------------------------------------------- +# Run the tests +#---------------------------------------------------------------------- +foreach my $test_href (@config_tests) +{ + if (exists $test_href->{COMMENT}) { + print CONFIG "\n$test_href->{COMMENT}\n"; + } else { + print CONFIG "\n// $test_href->{NAME}\n"; + } + + my $test_result = eval "$test_href->{TEST}"; + if ($test_result != 0) + { + print CONFIG "$test_href->{SUCCESS}\n"; + } + else + { + print CONFIG "$test_href->{FAIL}\n"; + } +} + +print CONFIG "#endif // #ifndef __DBGNUB_CONFIG__\n"; +close(CONFIG); + diff --git a/lldb/tools/debugserver/source/ChangeLog b/lldb/tools/debugserver/source/ChangeLog new file mode 100644 index 00000000000..2f3843bbc60 --- /dev/null +++ b/lldb/tools/debugserver/source/ChangeLog @@ -0,0 +1,1515 @@ +2010-01-29 Greg Clayton <gclayton@apple.com> + + * MachProcess.cpp (MachProcess::PrepareForAttach): No longer use the + SBSLaunchApplication macro from the SpringBoard.framework, use the actual + function name SBSLaunchApplicationForDebugging. + (MachProcess::CleanupAfterAttach): Ditto. + (MachProcess::SBForkChildForPTraceDebugging): Ditto. + (debugserver-entitlements.plist): Added the "seatbelt-profiles" entitlement + so debugserver can be sandboxed. + +2009-07-06 Greg Clayton <gclayton@apple.com> + + * MachTask.cpp (MachTask::GetDYLDAllImageInfosAddress): Hack around bad + kernel code that renamed the first member of the TASK_DYLD_INFO without + any way to detect it has changed. + +2009-06-29 Greg Clayton <gclayton@apple.com> + + * DNB.cpp (GetAllInfosMatchingName): Correctly truncate process name string + to MAXCOMLEN when searching kinfo_proc structs for process matches by name. + * MachProcess.cpp (MachProcess::PrepareForAttach): Added logging when + attaching to a program by name. + +2009-06-25 Greg Clayton <gclayton@apple.com> + + * DNB.cpp (DNBProcessLaunch): Added a stat on the incoming path that we are + about to launch to make sure the file exists. If the file doesn't, then an + appropriate error string is returned. Also if we fail to get the task for + our process ID, we return an error string right away instead of letting the + debug session go for a little bit and then later failing after a few more + packets. + +2009-04-07 Jim Ingham <jingham@apple.com> + + * RNBRemote.h: Add vAttachWait + * RNBRemote.cpp (RNBRemote::CreatePacketTable): Add vattachwait. + (RNBRemoteShouldCancelCallback): New function. + (RNBRemote::HandlePacket_v): Handle vattachwait. + * RNBSocket.cpp (RNBSocket::Read): Mark the connection as closed when the + port goes away. + * DNB.cpp (DNBProcessAttachByName): New function. + (DNBProcessAttach): Make this handle catching the attach when done and + dealing with timeout & return conditions. + (GetAllInfos): New function. + (GetAlInfosMatchingName): New function. + (DNBProcessAttachWait): New function. + DNB.h: Declare DNBProcessAttachByName, DNBProcessAttachWait, change + signature of DNBProcessAttach. + * MachProcess.cpp (MachProcess::PrepareForAttach): New function. + (MachProcess::CheckForProcess): New function. + (MachProcess::CleanupAfterAttach): New function. + (CopyBundleIDForPath): New function. + (MachProcess::SBForkChildForPTraceDebugging): Convert to using + CopyBundleIDForPath. + * MachProcess.h: Declare PrepareForAttach, CleanupAfterAttach and + CheckForProcess. + * DNBTimer.h (TimeOfDayLaterThan): New function. + * test-remotenub.cpp (RNBRunLoopGetStartModeFromRemote): Rename from + RNBRunLoopGetArgsFromRemote, and handle vattachwait. + (RNBRunLoopLaunchAttaching): Code was moved from here into DNBProcessAttach. + (StartListening): New function. + (GetAllProcessInfos, GetAllProcessInfosMatchingName): Moved to + DNBProcess.cpp. + (main): Handle attach waitfor, and make debugserver with only a host and + port wait on commands from gdb. + +2009-04-03 Greg Clayton <gclayton@apple.com> + + * RNBRemote.h (PacketEnum): Added enum for qShlibInfoAddr. + * RNBRemote.cpp (RNBRemote::CreatePacketTable) Added the qShlibInfoAddr + packet definition to m_packets. + (RNBRemote::GetPacket): Log when we run into an unimplemented packet. + (RNBRemote::HandleReceivedPacket): Only log the packet when logging + LOG_RNB_REMOTE. + (RNBRemote::HandlePacket_q): Add support for the new qShlibInfoAddr packet. + * DNB.h (DNBProcessGetSharedLibraryInfoAddress): New prototype. + * DNB.cpp (DNBProcessGetSharedLibraryInfoAddress): New function. + * MachTask.h (MachProcess::GetDYLDAllImageInfosAddress): New prototype. + * MachTask.cpp (MachProcess::GetDYLDAllImageInfosAddress): New function. + +2009-04-01 Greg Clayton <gclayton@apple.com> + + * test-remotenub.cpp (main): Display the detailed error message if any when + attaching fails. + +2009-03-25 Greg Clayton <gclayton@apple.com> + + * test-remotenub.cpp (RNBRunLoopGetArgsFromRemote): Cleaned up logging and + removed time deltas form the messages. + (RNBRunLoopLaunchAttaching): Ditto. + (RNBRunLoopLaunchInferior): Ditto and also use new DNBProcessLaunch that + takes an error string pointer. + * RNBContext.h (class RNBContext): Removed the m_timer member. + * RNBContext.cpp (RNBContext::StartProcessStatusThread): Cleaned up logging + and removed time deltas form the messages. + (RNBContext::ThreadFunctionProcessStatus): Ditto. + * RNBSocket.h (class RNBSocket): Removed unused m_last_errno member and + accessor functions. + * RNBSocket.cpp (RNBSocket::Listen): Cleaned up logging and + removed time deltas form the messages. + (RNBSocket::ConnectToService): Ditto. + (RNBSocket::Read): Ditto. + (RNBSocket::Write): Ditto. + (RNBSocket::SaveErrno): Removed. + (RNBSocket::ClosePort): Don't call RNBSocket::SaveErrno(). + * RNBRemote.cpp (RNBRemote::RNBRemote): Cleaned up logging and + removed time deltas form the messages. + (RNBRemote::~RNBRemote): Ditto. + (RNBRemote::SendPacket): Ditto. + (RNBRemote::GetPacketPayload): Ditto. + (RNBRemote::GetPacket): Ditto): Ditto. + (RNBRemote::HandleAsyncPacket): Ditto. + (RNBRemote::HandleReceivedPacket): Ditto. + (RNBRemote::CommDataReceived): Ditto. + * DNB.cpp (DNBProcessLaunch): Changed to take a eror string pointer with + size for more desciptive error reporting (instead of a uint32_t pointer). + * DNB.h (DNBProcessLaunch): Ditto. + * DNBError.cpp (DNBError::AsString): Now returns NULL if there is no error. + * DNBError.h (DNBError::SetErrorString): New accessor to allow custom error + strings. + * arm/DNBArchImpl.cpp (DNBArchMachARM::GetGPRState): Improved logging. + * MachProcess.cpp (MachProcess::SBForkChildForPTraceDebugging): Improved + error messages when a file doesn't exist, or when unable to extract the + CFBundleIdentifier. + * PThreadEvent.cpp (class PThreadEvent): Commented out all logging calls. + +2009-03-07 Greg Clayton <gclayton@apple.com> + + * test-remotenub.cpp (GetAllProcessInfosMatchingName): New function that + returns matching kinfo_proc structs given a process name. + (main): Enhanced the --attach option to be able to take a PROCNAME or + a PID. Changed the --waitfor=PROCNAME option to ignore any existing + processes with PROCNAME so we only catch new process invocations. + +2009-03-07 Greg Clayton <gclayton@apple.com> + + * RNBRemote.cpp (RNBRemote::HandlePacket_p): Use the correct get current + thread function call so we get the correct thread registers. + +2009-03-03 Greg Clayton <gclayton@apple.com> + + * test-remotenub.cpp (g_isatty): New global that gets set to non-zero if + STDOUT is a TTY in the beginning of main. + (RNBLogSTDOUT): New macro that logs to STDOUT if g_isatty is non-zero, else + it logs to asl. + (RNBLogSTDERR): New macro that logs to STDERR if g_isatty is non-zero, else + it logs to asl. + (RNBRunLoopGetArgsFromRemote): Use new RNBLogSTDOUT/RNBLogSTDERR macros. + (GetAllProcessInfos): Get all process info structs for everything on the + system. + (main): Implemented new --waitfor=NAME option to allow waiting for a process + to run by polling the system processes. The new --waitfor-interval=N option + allows fine control over the polling interval where N is the number of mirco + seconds (usec) to wait between polls (defaults to 1000). The new + --waitfor-duration=N allows a timeout in seconds to be specified when + waiting for a process (defaults to infinite). + +2009-03-02 Greg Clayton <gclayton@apple.com> + + * DNBArchImpl.cpp (DNBArchMachARM::EvaluateNextInstructionForSoftwareBreakpointSetup): + Take care of a case where no instructions execute in a Thumb IT block and + the last of which is a branch. + +2009-02-10 Greg Clayton <gclayton@apple.com> + + * RNBRemote.h (PacketEnum): Added 'detach' enumeration. + (RNBRemote::HandlePacket_D): New member function prototype. + * RNBRemote.cpp (RNBRemote::CreatePacketTable): Added detach support. + (RNBRemote::HandlePacket_D): New function for detach support. + +2009-02-10 Greg Clayton <gclayton@apple.com> + + * RNBRemote.cpp (RNBRemote::HandlePacket_UNIMPLEMENTED): Log this + packet with the packet that is unimplemented. + (RNBRemote::GetPacket): Call RNBRemote::HandlePacket_UNIMPLEMENTED() + when we don't recognize a packet. + (RNBRemote::HandleReceivedPacket): Don't reply to packets we don't + recognize with unimplemented in this fucntion as that should have + already been done for us in RNBRemote::GetPacket(). + +2009-02-10 Greg Clayton <gclayton@apple.com> + + * RNBRemote.h (PacketEnum): Added query_step_packet_supported. + * RNBRemot.cpp (RNBRemote::CreatePacketTable): Added new + qStepPacketSupported packet. + (RNBRemote::HandlePacket_q): Added support for the new + "qStepPacketSupported" packet. + (RNBRemote::HandlePacket_G): Some cleanup when reading registers + to avoid spurious console logging. + +2009-01-30 Greg Clayton <gclayton@apple.com> + + * debugserver-entitlements.plist: Changed the entitlement + "run-invalid-allow" to "run-unsigned-code". + +2009-01-23 Greg Clayton <gclayton@apple.com> + + * DNBArchImpl.cpp (DNBArchMachARM::EvaluateNextInstructionForSoftwareBreakpointSetup): + Merged Yusuf's changes to make software single stepping work. + * test-remotenub.cpp (RNBRunLoopLaunchInferior): Call new + DNBResolveExecutablePath function to resolve executable paths. + * DNB.h (DNBResolveExecutablePath): New function prototype. + * DNB.cpp (DNBResolveExecutablePath): New function that will resolve + relative paths and also executable paths for executables that aren't relative + but yet are in the shell PATH environment variable. + +2009-01-22 Greg Clayton <gclayton@apple.com> + + * DNBArchImpl.h (class DBNArchMachARM): Renamed member variable + m_chained_hw_single_step_addr to m_hw_single_chained_step_addr. Added + new member variables: m_sw_single_step_itblock_break_id, m_last_decode_pc, + and m_sw_single_step_itblock_break_count. Renamed m_thumbStaticData to + m_last_decode_thumb, and renamed m_decodedInstruction to m_last_decode_arm. + (DBNArchMachARM::DecodeITBlockInstructions): New prototype. + (DBNArchMachARM::DecodeInstructionUsingDisassembler): New prototype. + (DBNArchMachARM::BreakpointHit): New prototype. + * DNBArchImpl.cpp (DNBArchMachARM::ThreadDidStop): Disable any of the + many software single step breakpoints if any are set. + (DNBArchMachARM::StepNotComplete): Changed renamed member accesses. + (DNBArchMachARM::DecodeITBlockInstructions): New function for software + single stepping through Thumb IT blocks. + (DNBArchMachARM::EnableHardwareSingleStep): Cleaned up logging. + (DNBArchMachARM::ComputeNextPC): Ditto. + (DNBArchMachARM::EvaluateNextInstructionForSoftwareBreakpointSetup): Now + properly handles Thumb IT software single stepping. + (DNBArchMachARM::SetSingleStepSoftwareBreakpoints): Ditto. + (DNBArchMachARM::DecodeInstructionUsingDisassembler): New function. + (DNBArchMachARM::BreakpointHit): New breakpoint callback function. + +2009-01-21 Greg Clayton <gclayton@apple.com> + + * MachProcess.cpp (MachProcess::PrivateResume): Set the process state before + we actually resume so we are sure to get the events in the correct order. + +2009-01-16 Greg Clayton <gclayton@apple.com> + + * RNBRemote.cpp (RNBRemote::HandlePacket_last_signal): Include only + registers which are to be expedited in the T packets. + (RNBRemote::HandlePacket_p): Enable for all targets. + (struct register_map_entry): Added an expedite member so we know which + registers need to be sent up to the host with each stop reply packet. + (register_map): Updated each array members' expedite member with an + appropriate value. + +2009-01-16 Greg Clayton <gclayton@apple.com> + + * RNBRemote.cpp (RNBRemote::HandlePacket_s): Enabled the step command ("s" + packet) for ARM now that libdebugnub.dylib can do both hardware and software + single stepping. + +2009-01-13 Greg Clayton <gclayton@apple.com> + + *DNBArchImpl.cpp (bit): New function. + (bits): New function. + (DNBArchMachARM::ConditionPassed): Use new "bit" function. + (DNBArchMachARM::ComputeNextPC): Use new "bit" function, remove inline + assembly for "RSC" instruction so this compiles for armv7 (which defaults + to thumb) + (DNBArchMachARM::NumSupportedHardwareBreakpoints): Use new "bits" function. + (DNBArchMachARM::NumSupportedHardwareWatchpoints): Use new "bits" function. + +2009-01-12 Greg Clayton <gclayton@apple.com> + + * DNBArch.h (DNBArchProtocol::NumSupportedHardwareBreakpoints()): Removed + the "const" qualifier to allow arches to auto detect how many hardware + breakpoints they have. + (DNBArchProtocol::NumSupportedHardwareWatchpoints()): Removed the "const" + qualifier to allow arches to auto detect how many hardware watchpoints they + have. + * DNBArchImpl.h (DNBArchMachARM::NumSupportedHardwareBreakpoints()): Auto + detect how many BRP pairs are avialable and disable for armv7 for the time + being (rdar://problem/6372672). + (DNBArchMachARM::NumSupportedHardwareWatchpoints()): Auto detect how many + WRP pairs are avialable and disable for armv7 for the time being + (rdar://problem/6372672). + +2009-01-09 Greg Clayton <gclayton@apple.com> + + * test-remotenub.cpp (main): Filled in short argument versions for + --applist (-t) and --lockdown (-k) options. + * DNBArchImpl.h (DNBArchMachARM::ConditionPassed): New protected + member function. + (DNBArchMachARM::ComputeNextPC): New protected member function. + (DNBArchMachARM::EvaluateNextInstructionForSoftwareBreakpointSetup): New + protected member function. + (DNBArchMachARM::m_thumbStaticData): New protected member variable. + (DNBArchMachARM::m_decodedInstruction): New protected member variable. + * DNBArchImpl.cpp (DNBArchMachARM::ThreadDidStop): Added extra code that + will log and exit when we are verifying software single stepping (a + compile time option). + (DNBArchMachARM::ConditionPassed): New function. + (DNBArchMachARM::ComputeNextPC): New function. + (DNBArchMachARM::EvaluateNextInstructionForSoftwareBreakpointSetup): New + function. + (DNBArchMachARM::SetSingleStepSoftwareBreakpoints): Added the guts of the + software single stepping. + (DNBArchMachARM::NumSupportedHardwareBreakpoints): Prepared for adding + auto detection code. + (DNBArchMachARM::NumSupportedHardwareWatchpoints): Prepared for adding + auto detection code. + +2008-12-11 Greg Clayton <gclayton@apple.com> + + * DNB.h (DNBProcessWaitForEvent): Renamed to DNBProcessWaitForEvents. + (DNBProcessSetEvents): Removed (deprecated). + (DNBProcessGetWaitForResetMask): Removed (unused). + (DNBProcessSetWaitForResetMask): Removed (unused). + (DNBProcessInterruptEvents): New function prototype. + * DNB.cpp (DNBProcessWaitForEvent): Renamed to DNBProcessWaitForEvents. + (DNBProcessSetEvents): Removed (deprecated). + (DNBProcessGetWaitForResetMask): Removed (unused). + (DNBProcessSetWaitForResetMask): Removed (unused). + (DNBProcessInterruptEvents): New function that can be used to + asynchronously interrupt infinite wait for events calls. + RNBRemote.cpp (RNBRemote::HandlePacket_v): Call DNBProcessWaitForEvents. + RNBContext.cpp (RNBContext::ThreadFunctionProcessStatus): Ditto. + test-remotenub.cpp (RNBRunLoopLaunchInferior): Ditto. + (RNBRunLoopLaunchAttaching): Ditto. + +2008-12-11 Greg Clayton <gclayton@apple.com> + + * DNB.cpp (GetProcessMap): Use new PTHREAD_MUTEX_LOCKER macro to ease + debugging of deadlocks. + (DNBProcessLaunch): Improved logging. + (DNBProcessMemoryRead): Call MachProcess::ReadMemory so breakpoint + opcodes can be removed from memory. + (DNBProcessMemoryWrite): Call MachProcess::WriteMemory so that we work + around enabled software breakpoint traps. + * DNBLog.cpp (GetLogThreadedMutex): New function. + (_DNBLogThreaded): Use new PTHREAD_MUTEX_LOCKER macro to ease + debugging of deadlocks. + (_DNBLogThreadedIf): Ditto. + * DNBBreakpoint.h (DNBBreakpoint::IntersectsRange): New function. + * DNBBreakpoint.cpp (DNBBreakpointList::FindIDByAddress): Improved + logging. + * MacOSX/MachThread.cpp (MachThread::MachThread): Improved logging. + (MachThread::~MachThread): Ditto. + (MachThread::Suspend): Ditto. + (MachThread::Resume): Ditto. + (MachThread::RestoreSuspendCount): Ditto. + (MachThread::GetState): Use new PTHREAD_MUTEX_LOCKER macro to ease + debugging of deadlocks. + (MachThread::SetState): Ditto. + * MacOSX/MachVMMemory.cpp (MachVMMemory::Read): Improved logging. + (MachVMMemory::Write): Ditto. + (MachVMMemory::WriteRegion): Ditto. + * MacOSX/MachProcess.cpp (MachProcess::GetState): Use new + PTHREAD_MUTEX_LOCKER macro to ease debugging of deadlocks. + (MachProcess::SetState): Ditto. + (MachProcess::Clear): Ditto. + (MachProcess::PrivateResume): Ditto. + (MachProcess::ReplyToAllExceptions): Ditto. + (MachProcess::ExceptionMessageReceived): Ditto. + (MachProcess::AppendSTDOUT): Ditto. + (MachProcess::GetAvailableSTDOUT): Ditto. + (MachProcess::ThreadFunctionSTDIO): Renamed from to + MachProcess::STDIOThread. + (MachProcess::StartSTDIOThread): Improved logging. + (MachProcess::CreateBreakpoint): Ditto. + (MachProcess::CreateWatchpoint): Ditto. + (MachProcess::DisableAllBreakpoints): Ditto. + (MachProcess::DisableBreakpoint): Ditto. + (MachProcess::DisableWatchpoint): Ditto. + (MachProcess::EnableBreakpoint): Ditto. + (MachProcess::EnableWatchpoint): Ditto. + (MachProcess::LaunchForDebug): Ditto. + (MachProcess::PosixSpawnChildForPTraceDebugging): Ditto. + (MachProcess::Detach): Reset the running event bit after resuming prior + to issuing the SIGSTOP to avoid a pause. + (MachProcess::RemoveTrapsFromBuffer): New function that removes + breakpoint traps from a memory buffer. + (MachProcess::ReadMemory): Read memory from the task, then removes any + breakpoint traps prior to returning the buffer. + (MachProcess::WriteMemory): Write memory and any needed data to the + breakpoint saved opcodes for any software breakpoint traps that are + enabled. + * MacOSX/MachProcess.h (MachProcess::ThreadFunctionException): Removed. + (MachProcess::ThreadFunctionSTDIO): Renamed to MachProcess::STDIOThread(). + (MachProcess::RemoveTrapsFromBuffer): New function. + * MacOSX/MachVMRegion.cpp (MachVMRegion::SetProtections): Improved + logging. + (MachVMRegion::RestoreProtections): Ditto. + (MachVMRegion::GetRegionForAddress): Ditto. + * MacOSX/MachException.cpp (catch_mach_exception_raise_state): Improved + logging. + (catch_mach_exception_raise_state_identity): Ditto. + (catch_mach_exception_raise): Ditto. + (MachException::Message::Dump): Ditto. + (MachException::Data::GetStopInfo): Ditto. + (MachException::Message::Receive): Ditto. + (MachException::Message::Reply): Ditto. + (MachException::Data::Dump): Ditto. + (MachException::PortInfo::Save): Ditto. + (MachException::PortInfo::Restore): Ditto. + * MacOSX/MachTask.cpp (MachTask::Suspend): Improved logging. + (MachTask::Resume): Ditto. + (MachTask::ReadMemory): Ditto. + (MachTask::WriteMemory): Ditto. + (MachTask::TaskPortForProcessID): Ditto. + (MachTask::BasicInfo): Ditto. + (MachTask::StartExceptionThread): Ditto. + (MachTask::ShutDownExcecptionThread): Ditto and use pthread_cancel to + interrupt the exception thread. + (MachTask::ExceptionThread): Ditto and revert back to infinite timeout + as pthread_cancel will break us out of infinite mach_msg receive calls. + * MacOSX/MachThreadList.cpp (MachThreadList::UpdateThreadList): Improved + logging. + (MachThreadList::CurrentThread): Use new PTHREAD_MUTEX_LOCKER macro to + ease debugging of deadlocks. + * DNBTimer.h (DNBTimer::DNBTimer): Initialize the mutex with a recursive + pthread. + (DNBTimer::Reset): Use new PTHREAD_MUTEX_LOCKER macro to ease debugging + of deadlocks. + (DNBTimer::TotalMicroSeconds): Ditto. + (DNBTimer::GetTime): Ditto. + (DNBTimer::ElapsedMicroSeconds): Ditto. + (DNBTimer::GetTimeOfDay): New class function. + * DNBError.cpp (DNBError::LogThreaded): Improved logging. + * test-dbgnub.cpp + * PThreadMutex.h: Added the ability to debug deadlocks by defining + DEBUG_PTHREAD_MUTEX_DEADLOCKS. + * FunctionProfiler.cpp + * PThreadEvent.cpp (PThreadEvent::NewEventBit): Use new + PTHREAD_MUTEX_LOCKER macro to ease debugging of deadlocks. + (PThreadEvent::FreeEventBits): Ditto. + (PThreadEvent::GetEventBits): Ditto. + (PThreadEvent::ReplaceEventBits): Ditto. + (PThreadEvent::SetEvents): Ditto. + (PThreadEvent::ResetEvents): Ditto. + (PThreadEvent::WaitForSetEvents): Ditto. + (PThreadEvent::WaitForEventsToReset): Ditto. + +2008-12-05 Greg Clayton <gclayton@apple.com> + + * DNBDefs.h (LOG_TASK): New log bit. + * DNB.cpp (DNBProcessIsAlive): User newly abtracted MachTask class. + (DNBProcessMemoryRead): Ditto. + (DNBProcessMemoryWrite): Ditto. + * DNBArchImpl.cpp (DNBArchMachARM::EnableHardwareSingleStep): Ditto. + (DNBArchMachARM::SetSingleStepSoftwareBreakpoints) Ditto. + * MachException.cpp (MachException::Message::Receive): Cleaned up logging + so it doesn't always log timeout errors. + (MachException::Message::Reply): Use abstracted MachTask class for any + task related queries. + (MachException::PortInfo::Save): Cleaned up logging. + (MachException::PortInfo::Restore): Cleaned up logging and now return an + error instead of the number of restored port infos. + * MachProcess.cpp (class MachProcess): Abstracted out all of the task_t + related stuff (suspend, resuyme, exception ports, exception thread, and + more) into a new class MachTask. + (MachProcess::Task): Now returns a reference to a MachTask class. + (MachProcess::Clear): Uses new abstracted MachTask class. + (MachProcess::Detach): Ditto. + (MachProcess::PrivateResume): Ditto. + (MachProcess::DisableBreakpoint): Ditto. + (MachProcess::ExceptionMessageReceived): Ditto. + (MachProcess::ExceptionMessageBundleComplete): Ditto. + (MachProcess::AttachForDebug): Ditto. + (MachProcess::LaunchForDebug): Ditto. + (MachProcess::SBLaunchForDebug): Ditto. + (MachProcess::TaskIsValid): Removed (replaced by similar functionality + in the new MachTask class). + (MachProcess::ExceptionPort): Ditto. + (MachProcess::ExceptionPortIsValid): Ditto. + (MachProcess::StartExceptionThread): Ditto. + (MachProcess::Suspend): Ditto. + (MachProcess::TaskResume): Ditto. + (MachProcess::TaskBasicInfo): Ditto. + (MachProcess::TaskBasicInfo): Ditto. + (MachProcess::ReadMemory): Ditto. + (MachProcess::WriteMemory): Ditto. + (MachProcess::ThreadFunctionException): Ditto. + +2008-12-04 Greg Clayton <gclayton@apple.com> + + * DNB.h (DNBProcessSetEvents): New API function prototype. + * DNB.cpp (DNBProcessSetEvents): New API function. + (DNBProcessHalt): Send our process a SIGINT instead of suspending + the task. + * DNBDefs.h (NUB_STATE_IS_STOPPED): Removed up duplicate entry in macro. + (eEventPrcoessAsyncInterrupt): New prcoess event bit that allows async + interrupting of infinite DNBProcessWaitForEvent() function calls. + * MachException.cpp (MachException::Message::Receive): Improved logging. + (MachException::Message::Reply): Improved logging. + * MachProcess.h (MachProcess::TaskBasicInfo): New member and static + functions. + * MachProcess.cpp (MachProcess::TaskIsValid): Use new TaskBasicInfo() + member function. + (MachProcess::Resume): Removed the detach parameter from the PrivateResume() + function call. + (MachProcess::Kill): Added a absolute timeout pointer to allow callers to + wait for the signal to be received if the timeout is non-NULL. + (MachProcess::TaskBasicInfo): New member and static function. + (MachProcess::TaskResume): New function that resumes the task by making sure + the suspend count is correctly ref counted. + (MachProcess::Detach): When detaching from a process make sure it is + stopped (SIGSTOP) first, then we can successfully detach. The exception + thread now also properly exits. + (MachProcess::PrivateResume): Call new TaskResume function, and removed the + detach functionality. + (MachProcess::DisableBreakpoint): Only notify the thread list that a + breakpoint has changed if the breakpoint is going to be removed. + (MachProcess::ThreadFunctionException): Added a permanent 1 second timeout + for each call to mach_msg() so we can exit the thread in the event that + we detach from a process/task. + * test-debugnub (main): Modified to show an example of how to detach using + a signal_handler to asynchronously receive a SIGINT and properly interrupt + and detach from a running process. + +2008-11-26 Greg Clayton <gclayton@apple.com> + + * DNBDefs.h (LOG_STEP): New logging define. + * DNBError.cpp (DNBError::LogThreaded): If there is no error, then + log with "success: " as a prefix instead of "error: ". + * arm/DBNArchImpl.cpp (DNBArchMachARM::EnableHardwareSingleStep): Log using + new LOG_STEP instead of LOG_BREAKPOINTS. + (DNBArchMachARM::SetSingleStepSoftwareBreakpoints): Ditto. + * MachException.cpp (MachException::Message::Dump): Log excetion header + and reply header on two separate lines. + * MachProcess.cpp (IsSBProcess): Check for NULL CFArrayRef returned from + SBSCopyApplicationDisplayIdentifiers for SkankPhone. + (MachProcess::Suspend): Check if process state is not running instead of + having to receive an event after a timeout if one is given. + (MachProcess::Detach): Deallocate the exception port when detaching and + restore the inferior task exception ports prior to clearing and detaching. + (MachProcess::PrivateResume): Grab the task's basic info and make sure we + get the resume the correct number of times. + (MachProcess::DisableBreakpoint): Removed unused variable opcode_restored + and make sure the breakpoint is enabled before we start warning that + our opcode wasn't there. + * ppc/DBNArchImpl.cpp (DNBArchMachPPC::EnableHardwareSingleStep): Log + using LOG_STEP instead of LOAD_BREAKPOINTS. + * RNBServices.cpp (IsSBProcess): Check for NULL CFArrayRef returned from + SBSCopyApplicationDisplayIdentifiers for SkankPhone. + +2008-11-26 Greg Clayton <gclayton@apple.com> + + * MachProcess.h (MachProcess::Suspend): Now takes an optional absolute + timeout that, if non-NULL, will case the function to return after the + process has been suspended and is in a stopped state. If the timeout is + NULL, then no waiting will occur. + * MachProcess.cpp (MachProcess::Suspend): Ditto. + (MachProcess::Detach): Now replies to all exceptions, un-suspends all + threads and resumes the task. + (MachProcess::ReplyToAllExceptions): New function. + (MachProcess::PrivateResume): Now takes an additional parameter named + detach that will do the right thing when detaching from a process. + * DNBArchImpl.h (DNBArchMachI386::ThreadWillResume): Returns void. + * DNBArchImpl.cpp (DNBArchMachI386::ThreadWillResume): Returns void. + * RNBServices.cpp (ListApplications): #ifdef-ed for ARM only as it + currently uses SpringBoard. + (IsSBProcess): Ditto. + * test-remotenub.cpp (RNBRunLoopLaunchInferior): #ifdef-ed around + ARM parts so it compiles for i386. + (main): Ditto. + +2008-11-24 Greg Clayton <gclayton@apple.com> + + * DNBArchProtocol.h (DNBArchProtocol::ThreadWillResume): Now returns void. + * DNBArchImpl.cpp (DNBArchMachARM::ThreadWillResume): Returns void and + has hollowed out support for software single step. + (DNBArchMachARM::ThreadDidStop): Has a debug mode that uses hardware single + step to verify software single step that can be enabled by defining + DNB_ARCH_MACH_ARM_DEBUG_SW_STEP. + (DNBArchMachARM::SetSingleStepSoftwareBreakpoints): New function. + * DNBArchImpl.h (DNBArchMachARM::ThreadWillResume): Returns void. + (DNBArchMachARM::SetSingleStepSoftwareBreakpoints): New prototype. + (DNBArchMachARM::m_sw_single_step_next_pc): New member variable. + (DNBArchMachARM::m_sw_single_step_break_id): New member variable. + * MachThread.cpp (MachThread::ThreadWillResume): Now returns void. + * MachThread.h (MachThread::ThreadWillResume): Now returns void. + +2008-11-19 Greg Clayton <gclayton@apple.com> + + * DNBError.h (FlavorType): Added SpringBoard error type for arm builds. + * DNBError.cpp (DNBError::AsString): Now returns SpringBoard error strings + if the error type is SpringBoard. + * test-remotenub.cpp (RNBRunLoopLaunchInferior): Set the error into + RNBContext as either a POSIX error or a SpringBoard error. + * RNBContext.h (m_launch_status): Changed this member to be a DNBError + instead of a uint32_t. + (RNBContext::LaunchStatus): Now returns a reference to the DNBError object + in m_launch_status. + * RNBContext.cpp (RNBContext::LaunchStatusAsString): Let DNBError handle + any error string descriptions, including SpringBoard errors. + * RNBRemote.cpp (RNBRemote::HandlePacket_q): Use new error class in + RNBContext. + (RNBRemote::HandlePacket_C): Return without an erroneous error when resuming + a process with a signal. + * DNBArch.h (DNBArchProtocol::StepNotComplete): New protocol function with + default return value. + * DNBArchImpl.cpp (DNBArchMachARM::StepNotComplete): New function. + (DNBArchMachARM::EnableHardwareSingleStep): Handle hardware single stepping + over 32 bit thumb instructions better so we always do a true instruction + level single step. + * MachProcess.cpp (MachProcess::ExceptionMessageBundleComplete): Now resumes + if single stepping wasn't able to complete in a single run. + * MachThread.cpp (MachThread::ShouldStop): Fills in new step_more parameter + if stepping is not complete. + * MachThreadList.cpp (MachThreadList::ShouldStop): Pass step_more parameter + to each MachThread::ShouldStop call. + +2008-11-13 Greg Clayton <gclayton@apple.com> + + * MachProcess.cpp (MachProcess::PosixSpawnChildForPTraceDebugging): Don't + call posix_spawnattr_setbinpref_np when launching with posix_spawn on ARM + targets as it currently selects the incorrect slice due to multiple slices + that contain the same cputype, yet they all have differing cpusubtypes. + +2008-11-04 Greg Clayton <gclayton@apple.com> + + * RNBRemote.h (GetContinueThread): Don't return the current thread when + the continue thread is zero or -1. + * RNBRemote.cpp (RNBRemote::HandlePacket_c): Resume the process if we + have no continue thread set. + (RNBRemote::HandlePacket_s): Ditto. + (RNBRemote::HandlePacket_C): Ditto unless a continue address is specified + in which case we will only succeed if we have one thread when the continue + with signal and address doesn't have a continue thread specified. + (RNBRemote::HandlePacket_S): Ditto. + * DNB.cpp (DNBProcessResumeWithSignal): New function. + (DNBProcessResume): Added better logging. + (DNBProcessHalt): Ditto. + (DNBThreadResume): Ditto. + (DNBThreadResumeWithSignal): Ditto. + * DNB.h (DNBProcessResumeWithSignal): New prototype. + * DNBError.cpp (DNBError::LogThreaded): New function. + * DNBError.h (DNBError::LogThreaded): New prototype. + * DNBLog.cpp (_DNBLogThreaded): Added sequence ID for threaded logs. + (_DNBLogThreadedIf): Ditto. + * MachException.cpp (MachException::Data::GetStopInfo): Use new SoftSignal() + accessor. + (MachException::Data::DumpStopReason): Ditto. + (MachException::Message::Reply): Added better logging and log using the + soft signal if our task matches that in the exception. + (MachException::Data::Dump): Added better logging. + * MachException.h (IsSoftSignal): Removed. + (SoftSignal): New function that returns the soft signal in the exception + data if there is one, or zero otherwise. + * MachProcess.cpp (MachProcess::Suspend): Improved logging. + (MachProcess::Resume): Ditto. + (MachProcess::PrivateResume): Handle the case where the process is told + to resume with a signal by matching the signal up to the thread that had + the soft signal if no thread id is specified. + * MachThread.cpp (MachThread::Suspend): Improved logging. + (MachThread::Resume): Improved logging. + (MachThread::RestoreSuspendCount): Improved logging. + (MachThread::Resume): Improved logging. + (MachThread::Dump): Improved logging. + * MachThreadList.cpp (MachThreadList::Dump): Improved logging. + +2008-10-22 Greg Clayton <gclayton@apple.com> + + * test-remotenub.cpp (RNBRunLoopMode): Added a new enum value + eRNBRunLoopModeInferiorAttaching. + (g_long_options): Added "--attach=PID" for attaching to existing processes + and "--launch=(auto|posix|fork|springboard)" options. + (RNBRunLoopLaunchInferior): Now launches process with new + nub_launch_flavor_t enum that can be overridden with the --launch option. + (RNBRunLoopLaunchAttaching): New function for attaching to existing + processes. + (main): Added command line option support for the "--attach" and "--launch" + options and added attach to pid support and better logging. + * DNB.cpp/h: (DNBProcessLaunch): Added nub_launch_flavor_t and error + parameter for more precise control when launching processes. + (DNBProcessSBLaunch): Removed function as launching with SpringBoard can + now be done using DNBProcessLaunch with launch_flavor being set to + eLaunchTypeSpringBoard (arm only). + (DNBProcessSBAttach): Removed function (SpringBoard processes are now auto + detected in the MachProcess::AttachForDebug function on ARM). + * DNBDefs.h (NUB_GENERIC_ERROR): New generic error definition. + (nub_launch_flavor_t): New enumeration used for control over process + launching. + * MachProcess.cpp (IsSBProcess): New function. + (MachProcess::AttachForDebug): Removed flags parameter that was being used + for SpringBoard flags and we now detect if a process belongs to SpringBoard + by calling IsSBProcess. + (MachProcess::LaunchForDebug): Now has launch parameter that tells it how + to launch the inferior process and there is also an error code that gets + returned. This function can now launch using fork + exec, posix_spawn, + or SpringBoard on ARM targets. + (MachProcess::SBLaunchForDebug): Now uses DNBError reference instead of + uint32_t pointer for the error code. + (MachProcess::SBForkChildForPTraceDebugging): Ditto. + +2008-10-22 Greg Clayton <gclayton@apple.com> + + * MacOSX/arm/DNBArchImpl.cpp (DNBArchMachARM::GetRegisterValue): Set + register value to a uint32 value instead of a float64 value for s0 - + s31. + +2008-10-17 Greg Clayton <gclayton@apple.com> + + * test-remotenub.cpp (RNBRunLoopLaunchInferior): Don't listen for + the qLaunchSuccess if we aren't doing a lockdown connnection. + +2008-10-13 Greg Clayton <gclayton@apple.com> + + * RNBRemote.h (class RNBRemote): Added m_watchpoints member. + * DNB.cpp (DNBBreakpointSet): Added boolean hardware parameter for + requesting that a hardware breakpoint be set. + (DNBWatchpointSet): New function. + (DNBWatchpointClear): New function. + (DNBWatchpointGetHitCount): New function. + (DNBWatchpointGetIgnoreCount): New function. + (DNBWatchpointSetIgnoreCount): New function. + (DNBWatchpointSetCallback): New function. + (DNBWatchpointPrint): New function. + * DNBRegisterInfo.cpp (DNBRegisterValueClass::Dump): Modified to emit + a single DNBLog() call so there aren't multiple newlines when logging + to ASL. + * RNBContext.cpp (RNBContext::ThreadFunctionProcessStatus): Use new + process state changed events. + * DNBBreakpoint.h (class DNBBreakpoint): Removed m_state member and + added m_tid, m_enabled, m_hw_preferred, m_is_watchpoint, m_watch_read, + m_watch_write, and m_hw_index. + (DNBBreakpoint::ThreadID()): New accessor. + (DNBBreakpoint::IsEnabled()): New accessor. + (DNBBreakpoint::SetEnabled()): New accessor. + (DNBBreakpoint::IsWatchpoint()): New accessor. + (DNBBreakpoint::IsBreakpoint()): New accessor. + (DNBBreakpoint::SetIsWatchpoint()): New accessor. + (DNBBreakpoint::WatchpointRead()): New accessor. + (DNBBreakpoint::WatchpointWrite()): New accessor. + (DNBBreakpoint::HardwarePreferred()): New accessor. + (DNBBreakpoint::IsHardware()): New accessor. + (DNBBreakpoint::GetHardwareIndex()): New accessor. + (DNBBreakpoint::SetHardwareIndex()): New accessor. + (DNBBreakpoint::ThreadID()): New accessor. + (DNBBreakpoint::GetState()): Removed accessor. + (DNBBreakpoint::SetState()): Removed accessor. + (DNBBreakpoint::AddBreakpoint()): Renamed to Add(). + (DNBBreakpoint::RemoveBreakpoint()): Renamed to Remove(). + (DNBBreakpoint::FindBreakIDForAddress()): Renamed to FindIDByAddress(). + (DNBBreakpoint::ShouldStopAtBreakpoint()): Renamed to ShouldStop(). + (DNBBreakpoint::SetBreakpointCallback()): Renamed to SetCallback(). + (DNBBreakpoint::FindBreakpointWithAddress()): Renamed to + FindByAddress(). + (DNBBreakpoint::FindBreakpointWithBreakID()): Renamed to FindByID(). + (DNBBreakpoint::GetBreakpointAtIndex()): Renamed to GetByIndex(). + * FunctionProfiler.h: New header for subclass of DNBRuntimeAction. + * RNBRemote.cpp (RNBRemote::HandlePacket_v): Use new process state + changed events. + (RNBRemote::HandlePacket_z): Implement the hardware breakpoint and + watchpoint commands z1, Z1, z2, Z2, z3 and Z3 + * PThreadEvent.h (PThreadEvent::GetEventBits): Made member function + const. + (PThreadEvent::WaitForSetEvents): Ditto. + (PThreadEvent::WaitForEventsToReset): Ditto. + (PThreadEvent::WaitForResetAck): Ditto. + (PThreadEvent::m_mutex): Made class member mutable. + (PThreadEvent::m_set_condition): Made class member mutable. + (PThreadEvent::m_reset_condition): New mutable class member. + * ProfileObjectiveC.cpp + * DNBArch.h (DNBArch::NotifyException): Now has default implementation + that returns false. + (DNBArch::NumSupportedHardwareBreakpoints): New virtual member + function with a default implementation. + (DNBArch::NumSupportedHardwareWatchpoints): Ditto. + (DNBArch::EnableHardwareBreakpoint): Ditto. + (DNBArch::EnableHardwareWatchpoint): Ditto. + (DNBArch::DisableHardwareBreakpoint): Ditto. + (DNBArch::DisableHardwareWatchpoint): Ditto. + * DNB.h (DNBBreakpointSet): New take a HARDWARE parameter that allows + requests for setting hardware breakpoints. + (DNBWatchpointSet): New function prototype. + (DNBWatchpointClear): New function prototype. + (DNBWatchpointGetHitCount): New function prototype. + (DNBWatchpointGetIgnoreCount): New function prototype. + (DNBWatchpointSetIgnoreCount): New function prototype. + (DNBWatchpointSetCallback): New function prototype. + (DNBWatchpointPrint): New function prototype. + * MacOSX/arm/DNBArchImpl.cpp: Added hardware breakpoint and watchpoint + support for ARM. + (DNBArchMachARM::GetCPUType): New function. + (DNBArchMachARM::DumpDBGState): New function. + (DNBArchMachARM::GetDBGState): New function. + (DNBArchMachARM::SetDBGState): New function. + (DNBArchMachARM::EnableHardwareSingleStep): New function. + (DNBArchMachARM::EnableHardwareBreakpoint): New function. + (DNBArchMachARM::NotifyException): Removed. + (DNBArchMachARM::DisableHardwareBreakpoint): New function. + (DNBArchMachARM::EnableHardwareWatchpoint): New function. + (DNBArchMachARM::DisableHardwareWatchpoint): New function. + * MacOSX/MachThread.cpp (MachThread::Suspend): Added better logging. + (MachThread::Resume): Ditto. + (MachThread::RestoreSuspendCount): Ditto. + (MachThread::Dump): Ditto. + (MachThread::EnableHardwareBreakpoint): New function. + (MachThread::EnableHardwareWatchpoint): New function. + (MachThread::DisableHardwareBreakpoint): New function. + (MachThread::DisableHardwareWatchpoint): New function. + * MacOSX/MachThreadList.h (MachThreadList::GetLastError): Removed. + (MachThread::EnableHardwareBreakpoint): New prototype. + (MachThread::DisableHardwareBreakpoint): New prototype. + (MachThread::EnableHardwareWatchpoint): New prototype. + (MachThread::DisableHardwareWatchpoint): New prototype. + (class MachThread): Remove m_err member variable. + * MacOSX/ppc/DNBArchImpl.cpp (DNBArchMachPPC::GetCPUType) New + function. + (DNBArchMachPPC::NotifyException): Removed. + * MacOSX/ppc/DNBArchImpl.h (DNBArchMachPPC::NotifyException): Removed. + * MacOSX/MachThread.h (MachThread::EnableHardwareBreakpoint): New + prototype. + (MachThread::EnableHardwareWatchpoint): New prototype. + (MachThread::DisableHardwareBreakpoint): New prototype. + (MachThread::DisableHardwareWatchpoint): New prototype. + (class MachThread): Renambed class member m_exception to + m_stop_exception. + * MacOSX/MachProcess.cpp (MachProcess::SetState): Updated to use new + process event enumerations. + (MachProcess::PrivateResume): Added better logging. + (MachProcess::CreateBreakpoint): Added bool HARDWARE parameter for + requesting hardware breakpoints. + (MachProcess::CreateWatchpoint): New function. + (MachProcess::DisableAllWatchpoints): New function. + (MachProcess::DisableWatchpoint): New function. + (MachProcess::DumpWatchpoint): New function. + (MachProcess::EnableBreakpoint): Enabled breakpoints in hardware if + requested and supported. + (MachProcess::DisableBreakpoint): Disable hardware breakpoints if that + is how they were set. + (MachProcess::EnableWatchpoint): New function. + (MachProcess::ExceptionMessageBundleComplete): Wait for the + eEventProcessRunningStateChanged event to be reset before changing + state to stopped to avoid race condition with very fast start/stops. + (MachProcess::LaunchForDebug): Added posix_spawn support. + (MachProcess::PosixSpawnChildForPTraceDebugging): New function. + * MacOSX/i386/DNBArchImpl.cpp (DNBArchMachI386::GetCPUType): New + function. + * MacOSX/i386/DNBArchImpl.h (DNBArchMachI386::GetCPUType): New + prototype. + * MacOSX/MachProcess.h (PosixSpawnChildForPTraceDebugging): New + prototype. + * MacOSX/MachException.cpp (class MachException::ThreadMessage): + Renamed class to MachException::Data. + * MacOSX/MachThreadList.cpp (class MachThreadList): Removed m_err + class member. + (MachThreadList::EnableHardwareBreakpoint): New function. + (MachThreadList::DisableHardwareBreakpoint): New function. + (MachThreadList::EnableHardwareWatchpoint): New function. + (MachThreadList::DisableHardwareWatchpoint): New function. + * MacOSX/MachException.h (class MachException::ThreadMessage): + Renamed class to MachException::Data. + * DNBDefs.h (nub_watch_t): New typedef. + (INVALID_NUB_HW_INDEX): New macro definition. + (WATCH_TYPE_READ): New macro definition. + (WATCH_TYPE_WRITE): New macro definition. + (NUB_STATE_IS_RUNNING): New macro to see if state is a running state. + (NUB_STATE_IS_STOPPED): New macro to see if state is a stopped state. + (eEventProcessStateChanged): Deprecated. + (eEventProcessRunningStateChanged): New process event state. + (eEventProcessStoppedStateChanged): New process event state. + (LOG_WATCHPOINTS): New macro definition for logging watchpoints. + * test-remotenub.cpp (RNBRunLoopLaunchInferior): Use new process + event states. + * FunctionProfiler.cpp: New class that allows single stepping through + an address range for tracing exact call graphs. + +2008-09-22 Greg Clayton <gclayton@apple.com> + + * RNBRemote.h (GetContinueThread): If the continue thread is zero or + -1 then return GetCurrentThread(). + * RNBRemote.cpp (m_packets): Made the vCont functions call + RNBRemote::HandlePacket_v(). + (RNBRemote::HandlePacket_H): Cleaned up whitespace. + (RNBRemote::HandlePacket_last_signal): Return actual signal values for + EXE_SOFTWARE/EXC_SOFT_SIGNAL mach exceptions. + (RNBRemote::HandlePacket_v): Implemented the 'vCont?' and 'vCont;' + packets. + (RNBRemote::HandlePacket_c): Handle the case where an address is + provided. + (RNBRemote::HandlePacket_C): Implemented the continue with signal + including when an address is provided. + (RNBRemote::HandlePacket_S): Implemented the step with signal + including when an address is provided. + * DNB.cpp (DNBProcessResume): Pass 0 as the signal when resuming + a process without specifying a thread. + (DNBThreadResume): Pass 0 as the signal when resuming a specific thread. + (DNBThreadResumeWithSignal): New function. + * DNB.h (DNBThreadResumeWithSignal): New prototype. + * MachException.h (MachException::Message::Reply): Added a signal + parameter. + * MachException.cpp (MachException::Message::Reply): Update the thread + with the new SIGNAL parameter instead of always zero so signals can be + passed on to programs. + * MachProcess.h (MachProcess::Resume): Added a signal parameter. + * MachProcess.h (MachProcess::PrivateResume): Added a signal parameter. + * MachProcess.cpp (MachProcess::Resume): Pass new SIGNAL parameter to + MachProcess::PrivateResume. + * MachProcess.cpp (MachProcess::PrivateResume): Pass new SIGNAL + parameter to the mach exception reply. + +2008-08-08 Greg Clayton <gclayton@apple.com> + + * DNB.cpp (gProcessMap): Removed static C++ global. + (GetProcessMap): New Function. + (AddProcessToMap): New function. + (RemoveProcessFromMap): New function. + (GetProcessSP): Use new GetProcessMap function to get process list. + +2008-07-30 Greg Clayton <gclayton@apple.com> + + * debugserver-entitlements.plist (get-task-allow): Removed. + (run-invalid-allow): Added boolean value set to TRUE. + +2008-04-18 Greg Clayton <gclayton@apple.com> + + * MachProcess.cpp (MachProcess::Task): Added getuid(), geteuid(), + getgid(), getegid() to the log message if task for pid fails. + +2008-04-07 Greg Clayton <gclayton@apple.com> + + * RNBContext.cpp (RNBContext::LaunchStatusAsString): Removed unused + tmp_str variable. + +2008-04-04 Greg Clayton <gclayton@apple.com> + + * CFString.cpp/h (UTF8): Made a static function that can convert + a CFStringRef to UTF8. + +2008-04-04 Greg Clayton <gclayton@apple.com> + + * test-remotenub.cpp (main): Make sure we exit after we send the + application list. + +2008-04-04 Greg Clayton <gclayton@apple.com> + + * RNBServices.h (IsSBProcess): New prototype; + * RNBServices.cpp (IsSBProcess): New function that returns true it + SpringBoard owns or knows about the process. + * RNBRemote.cpp (RNBRemote::HandlePacket_v): Made attach work correctly. + * DNB.cpp (DNBProcessSBAttach): New function for use when attaching to + a process owned by SpringBoard. + (DNBProcessAttach): Fixed an issue where a local was shadowing a + parameter. + * DNB.h (DNBProcessSBAttach): New prototype. + * MachProcess.cpp (MachProcess::AttachForDebug): AttachForDebug now + takes some flags so it knows to enable SpringBoard functionality. + * MachProcess.h (MachProcess::AttachForDebug): Added flags parameter + to prototype. + +2008-04-04 Greg Clayton <gclayton@apple.com> + + * test-remotenub.cpp (RNBRunLoopGetArgsFromRemote): Handle the new + attach packet and watch for connection being lost. + (main): handle the --applist option when there we aren't using lockdown + by printing the results to stdout and exiting with appropriate error code + if we failed. Also handle the new prototype for ListApplications. + * RNBServices.h (ListApplications): Change first parameter to be a std::string + that will get the contents of the plist so we can use this for more than + just lockdown. + * RNBServices.cpp (ListApplications): Change first parameter to be a std::string + that will get the contents of the plist so we can use this for more than + just lockdown and also fixed the logic so we actually create a full list of + applications instead of just overwriting the first entry. + * RNBRemote.h (PacketEnum): Added a new 'vattach' enum for the "vAttach;PID" + gdb remote command. + (RNBRemote::HandlePacket_v): New prototype; + * RNBRemote.cpp (RNBRemote::CreatePacketTable): add the vattach packet definition + to m_packets. + (RNBRemote::HandlePacket_v): New function that handles attach to a process. + +2008-04-03 Jim Ingham <jingham@apple.com> + + * RNBRemote.h: Add query_launch_success to packet enum. + * RNBRemote.cpp (RNBRemote::CreatePacketTable_): Add query_launch_success. + (HandlePacket_q): Handle query_launch_success. + * DNB.cpp (DNBProcessSBLaunch): Pass in launch_retval. + * DNB.h: Change prototype of DNBProcessSBLaunch to take launch_retval. + * RNBContext.cpp (RNBContext::LaunchStatusAsString): New function. + * RNBContext.h (RNBContext): Add m_launch_status & accessors. + * macosx/MachProcess.cpp (MachProcess::SBLaunchForDebug): Pass launch_retval. + (MachProcess::SBForkChildForPTraceDebugging): Accept & set launch_retval. + * Macosx/MachProcess.h: Change prototypes of SBLaunchForDebug & + ForkChildForPTraceDebugging to accept launch_retval. + * test-remotenub.cpp (RNBRunLoopLaunchInferior): Get the launch status and + put it in the context, then wait for the qLaunchStatus packet. + +2008-04-03 Greg Clayton <gclayton@apple.com> + + * com.apple.debugserver.plist: Changed plist so debugserver + runs as mobile user. + * com.apple.debugserver.applist.plist: Ditto. + +2008-04-03 Greg Clayton <gclayton@apple.com> + + * MachProcess.cpp: (MachProcess::SBForkChildForPTraceDebugging): + Increased SBS application launch timeout to 30 seconds. + +2008-03-27 Christopher Friesen <friesen@apple.com> + + * RNBServices.h: Pass tasks from SpringBoard as a plist + * RNBServices.cpp: Ditto. + * test-remotenub.cpp: added --applist flag + * com.apple.debugserver.applist.plist: Agent plist + +2008-03-17 Jim Ingham <jingham@apple.com> + + * DNB.h: Pass envp to DNBProcessLaunch & DNBProcessSBLaunch. + * DNB.cpp: Ditto. + * MachProcess.h: Ditto for *LaunchForDebug and + *ForkChildForPtraceDebugging. + * MachProcess.cpp (MachProcess::LaunchForDebug): Pass on envp. + (MachProcess::SBLaunchForDebug): Ditto. + (MachProcess::ForkChildForPtraceDebugging): Accept envp, haven't actually + implemented the passing yet. + (MachProcess::SBForkChildForPtraceDebuggin): Accept envp, convert to + CFDictionary and pass to SBSLaunchApplication. + * RNBContext.h: Add environment to the context. + * RBNContext.cpp (RNBContext::EnvironmentAtIndex): New function. + * RNBRemote.h: Add set_environment_variable to the PacketEnum. + * RNBRemote.cpp (RNBRemote::CreatePacketTable): Add QEnvironment:. + * (RNBRemote::HandlePacket_Q): Ingest the environment variable. + * test-remotenub.cpp (RNBRunLoppLaunchInferior): Convert the env + array in the context into an array, and pass it to the DNBProcess*Launch + methods. + +2008-03-17 Greg Clayton <gclayton@apple.com> + + * DNBBreakpoint.cpp (DNBBreakpointList::GetBreakpointAtIndex): New + functions (const and non-const versions). + * DNBBreakpoint.h (DNBBreakpointList::GetBreakpointAtIndex): New + prototypes (const and non-const versions). + * DNBError.h (DNBError::Success()): Don't use KERN_SUCCESS define. + (DNBError::Fail()): Don't use KERN_SUCCESS define. + * MachProcess.cpp (MachProcess::DisableAllBreakpoints): New function. + (MachProcess::Detach): Added initial implementation that will halt + the process, disable all breakpoints and call PT_DETACH. + * MachProcess.h (MachProcess::DisableAllBreakpoints): New prototype. + +2008-03-04 Greg Clayton <gclayton@apple.com> + + * RNBRemote.h (RNBRemote::SendHexEncodedBytePacket): New prototype. + * RNBRemote.cpp (RNBRemote::SendHexEncodedBytePacket): New function. + (RNBRemote::SendSTDOUTPacket): Use SendHexEncodedBytePacket function + to send bytes. + (RNBRemote::SendSTDERRPacket): Ditto. + (RNBRemote::HandlePacket_q): Return a valid thread info string for + qThreadExtraInfo queries. + * DNB.cpp (DNBThreadPrintStopReason): Commented out unused function. + (DNBThreadGetInfo): New function. + * DNB.h (DNBThreadPrintStopReason): Commented out prototype. + (DNBThreadGetInfo): New prototype. + * MachProcess.cpp (MachProcess::GetThreadInfo): New function. + * MachProcess.h (MachProcess::GetThreadInfo): New prototype. + * MachThreadList.cpp (MachThreadList::GetThreadInfo): New function. + * MachThreadList.h (MachThreadList::GetThreadInfo): New prototype. + * MachThread.cpp (MachThread::GetBasicInfoAsString): New function. + (MachThread::InferiorThreadID): New function. + * MachThread.cpp (MachThread::GetBasicInfoAsString): New prototype. + (MachThread::InferiorThreadID): New prototype. + +2008-02-27 Greg Clayton <gclayton@apple.com> + + * RNBRemote.cpp (RNBRemote::HandlePacket_last_signal): Set the + current thread when we notify a thread has stopped to subsequent + g and p packets get the correct data. + +2008-02-26 Jason Molenda (jmolenda@apple.com) + + * RNBRemote.h: Add query_thread_extra_info enum. + * RNBRemote.cpp: Add support for qThreadExtraInfo. + Currently we return 'Ok' as the packet status for + every thread. + +2008-02-26 Jason Molenda (jmolenda@apple.com) + + * RNBRemote.cpp (RNBRemote::HandlePacket_q): Correct handling + of qfThreadInfo/qsThreadInfo. + +2008-02-20 Jason Molenda (jmolenda@apple.com) + + * RNBRemote.h: Change default for gdb's max incoming packet size to + reflect the real default size. + * RNBRemote.cpp (HandlePacket_Q): Correct the string comparisions for + the QSetMaxPayloadSize and QSetMaxPacketSize packets. + +2008-02-19 Christopher Friesen <friesen@apple.com> + + * CFDataFormatters.c: CoreFoundation data formatters added to project. + +2008-02-19 Jason Molenda (jmolenda@apple.com) + + * RNBRemote.h: Record the max payload size, not the max packet + size for less ambiguous meaning. + * RNBRemote.cpp: Add support for QSetMaxPayloadSize: packet which + should have a clearer meaning than QSetMaxPacketSize. + QSetMaxPacketSize will be removed once we get have a chance to get + a new debugserver and gdb submitted. + +2008-02-18 Jason Molenda (jmolenda@apple.com) + + * RNBRemote.h: Make default size 1024. + * RNBRemote.cpp: Questionmark packet should stay under + max_packet_size - 5 to allow for start, end, checksum and nul + char bytes. + +2008-02-18 Jason Molenda (jmolenda@apple.com) + + * RNBRemote.h: Add m_max_packet_size to class defn. + * RNBRemote.cpp: Initialize it, use it. + +2008-02-18 Jason Molenda (jmolenda@apple.com) + + * RNBRemote.h: Add set_max_packet_size. + * RNBRemote.cpp: Add QSetMaxPacketSize packet handling. + +2008-02-18 Greg Clayton <gclayton@apple.com> + + * test-remotenub.cpp (HandleProcessStateChange): Call new + RNBRemote::FlushSTDIO function. + (RNBRunLoopInferiorExecuting): Ditto. + * RNBRemote.h (RNBRemote::FlushSTDIO): New prototype. + * RNBRemote.cpp (RNBRemote::FlushSTDIO): New function to + centralize the stdio. + +2008-02-18 Greg Clayton <gclayton@apple.com> + + * DNB.cpp (DNBProcessWaitForEvent): Added timeout pointer as + parameter that can be NULL for infinite timeout to simplify + the DNB interface. + (DNBProcessTimedWaitForEvent): Removed function. + * DNB.h (DNBProcessWaitForEvent): Added timeout argument. + (DNBProcessTimedWaitForEvent): Removed prototype. + * DNBTimer.h (DNBTimer::OffsetTimeOfDay): New function. + * CFString.cpp (CFString::GetLength() const): New function. + * CFString.h (CFString::GetLength() const): New prototype. + * MachProcess.h (MachProcess class): Removed m_attached and + added m_flags. + * MachProcess.cpp (MachProcess::AttachForDebug): Set m_flags + to indicate we attached. + (MachProcess::SBLaunchForDebug): Set m_flags to indicate we + attached using SpringBoard and that we attached. + (MachProcess::SBForkChildForPTraceDebugging): Changed to new + SpringBoardServices API. + (MachProcess::ThreadFunctionException): Added code that will + renew a watchdog assertion when we launch apps through + SpringBoardServices. + * PThreadEvent.cpp (PThreadEvent::WaitForSetEvents): Simplified + PThreadEvent API to have only one version of WaitForSetEvents + that has an optional timeout pointer argument. + * RNBContext.cpp (RNBContext::StopProcessStatusThread): Adapt + to new PThreadEvent API changes. + (RNBContext::ThreadFunctionProcessStatus): Adapt to new + DNBProcessWaitForEvent API changes. + * RNBRemote.cpp (RNBRemote::StopReadRemoteDataThread): Adapt + to new PThreadEvent API changes. + * test-remotenub.cpp (RNBRunLoopLaunchInferior): Adapt to new + DNBProcessWaitForEvent API changes. + (RNBRunLoopInferiorExecuting): Process STDIO first, then + incoming packets. + +2008-02-14 Jason Molenda (jmolenda@apple.com) + + * MachProcess.cpp: (MachProcess::SBForkChildForPTraceDebugging): + Set mode bits on slave side of pty. + +2008-02-12 Greg Clayton <gclayton@apple.com> + + * DNB.cpp (DNBEnableLogging): Removed function. + (DNBThreadPrintStopReason): Removed the file handle from this + function and use DNBLog calls. + * DNB.h (DNBEnableLogging): Removed function prototype. + (DNBThreadPrintStopReason): Removed the file handle + from the function prototype in favor of using DNBLog calls. + * DNBDataRef.cpp (DNBDataRef::Dump): Removed file handle to use + DNBLog for the logging and print a log line each time a full line + is ready for output after caching it in a local buffer. + * DNBDataRef.cpp (DNBDataRef::Dump): Removed file handle from + prototype. + * DNBDefs.h (DNBCallbackLog): New callback prototype for all + logging. + DNBLog.cpp(g_debug_opt): Renamed to d_debug and made it a file + static. + (DNBLogGetDebug): New accessor function for g_debug. + (DNBLogSetDebug): New accessor function for g_debug. + (g_verbose): Made into a file static and added accessors. + (DNBLogGetVerbose): New accessor function for g_verbose. + (DNBLogSetVerbose): New accessor function for g_verbose. + (DNBLogSetLogCallback): New function call that registers a logging + callback for all logging in libdebugnub.dylib and any code that + loads it. + (DNBLogToASL): Removed function as it is deprecated in favor of + using DNBLogSetLogCallback to regsiter a callback function that + implements the logging. + (DNBLogToFile): Ditto. + (DNBLogCloseLogFile): Ditto. + (DNBLogToFile): Ditto. + (DNBLogToFile): Ditto. + (_DNBLogPuts): Removed unused function. + (_DNBLogVAPrintf): Calls the callback function to do the logging + if one has been registered. + * DNBLog.h (DNBLOG_FLAG_FATAL): New defines that get passed to + any registered logging callback functions. + (DNBLOG_FLAG_FATAL): Ditto. + (DNBLOG_FLAG_ERROR): Ditto. + (DNBLOG_FLAG_WARNING): Ditto. + (DNBLOG_FLAG_DEBUG): Ditto. + (DNBLOG_FLAG_VERBOSE): Ditto. + (DNBLOG_FLAG_THREADED): Ditto. + (DNBLog*): All logging calls are now exported from libdebugnub.dylib + so there aren't two copies (one in debugserver and one in debugnub). + C99 vararg Macros wrap all logging calls so no var arg processing + occurs when logging is disabled. + * DNBRegisterInfo.cpp (DNBRegisterValueClass::Dump): Removed file + handle and now use DNBLog calls. + * DNBRegisterInfo.h (DNBRegisterValueClass::Dump): Removed file + handle from prototype. + * MachException.cpp (catch_mach_exception_raise_state_identity): + Removed newlines from logging call. + (catch_mach_exception_raise): Ditto. + (MachException::Message::Dump): Removed file handle from params + and removed newlines from logging call. + (MachException::ThreadMessage::DumpStopReason): Removed file handle + from params and use DNBLog for logging output. + (MachException::ThreadMessage::Dump): Log using DNBLog instead of + file handle. + * MachProcess.cpp (MachProcess::DumpThreadStoppedReason): Ditto. + (MachProcess::ReadMemory): Ditto. + (MachProcess::WriteMemory): Ditto. + (ExceptionMessageBundleComplete): Ditto. + * MachThread.cpp (MachThread::Dump): Ditto. + (MachThread::DumpRegisterState): Ditto. + * MachThreadList.cpp (MachThreadList::DumpThreadStoppedReason): Ditto. + (MachThreadList::Dump): Ditto. + * RNBRemote.cpp (set_logging): Use new function callback registration + calls when enabling ASL logging. + test-remotenub.cpp (ASLLogCallback): New function to handle all ASL + logging. This function gets registered with libdebugnub.dylib when we + want to log using ASL. + (FileLogCallback): New function to handle all file logging. This + function gets registered with libdebugnub.dylib when we want to log + to a 'FILE *'. + (main): Register the logging callback functions when we want to log + to file or using ASL. + +2008-02-12 Greg Clayton <gclayton@apple.com> + + * test-remotenub.cpp (main): Default to ASL logging with no log + bits set to allow for warning and error logging. + * RNBRemote.h (struct Breakpoint): New structure for ref counting + breakpoints in Z and z packets. + * RNBRemote.cpp (RNBRemote::SendPacket): Use new LOG_RNB_PACKETS + defined when logging actual packet content. + (RNBRemote::HandleAsyncPacket): Ditto. + (RNBRemote::HandleReceivedPacket): Ditto. + (RNBRemote::HandlePacket_z): Ref count the setting and removing + of breakpoints with the Z and z packets using new struct + RNBRemote::Breakpoint. + * RNBDefs.h (LOG_RNB_PACKETS): New define for logging the sending + and receiving of packets data. + * DNB.cpp (DNBPrintf): Check for NULL file handle. + * DNBBreakpoint.cpp (DNBBreakpoint::Dump): Ditto. + (DNBBreakpointList::Dump): Ditto. + * DNBDefs.h (LOG_EVENTS): New define for logging PThreadEvent. + * DNBLog.cpp (g_debug_opt): Relocated outside of #if that turns off + logging completely to allow option parsing code that uses it to + still compile. + (g_verbose): Ditto. + * DNBLog.h (DNBLogToASL): Added prototype for when logging is + disabled via preprocessor macro. + (DNBLogToFile): Ditto. + * DNBRegisterInfo.cpp (DNBRegisterValueClass::Dump): Check for NULL + file handle. + * MachException.cpp (MachException::ThreadMessage::DumpStopReason): Ditto. + (MachException::ThreadMessage::Dump): Ditto. + * MachProcess.cpp (MachProcess::CreateBreakpoint): Improved logging. + (MachProcess::DisableBreakpoint): Verify the original opcode gets + restored, improved logging and added unconditional logging for when + things go wrong. + (MachProcess::EnableBreakpoint): Verify the breakpoint opcode gets + written, improved logging and added unconditional logging for when + things go wrong. + * MachThread.cpp (MachThread::Dump): Check for NULL file handle. + * MachVMMemory.cpp (MachVMMemory::WriteRegion): Flush caches in inferior + after writing to inferior memory. + * PThreadEvent.cpp: Changed all logging calls to key off of LOG_EVENTS + instead of LOG_VERBOSE. + MachDYLD.cpp (MachDYLD::Dump): Check for NULL file handle. + (MachDYLD::DYLIBInfo::Dump): Ditto. + ProfileObjectiveC.cpp (ProfileObjectiveC::DumpStats): Ditto. + +2008-02-09 Jason Molenda (jmolenda@apple.com) + + * RNBRemote.cpp (set_logging): Log to ASL unconditionally when + processing a QSetLogging packet. + +2008-02-06 Greg Clayton <gclayton@apple.com> + + * test-remotenub.cpp (main): Dup stdout and stderr to /dev/NULL + when we use lockdown. + +2008-02-06 Greg Clayton <gclayton@apple.com> + + * RNBSocket.cpp (RNBSocket::Disconnect): Removed unused var ERR. + * RNBRemote.cpp(RNBRemote::HandlePacket_Q): Removed unused var PID. + * DNBError.cpp (DNBError::LogThreadedIfError): Removed unused var + ERR_MSG. + * test-remotenub.cpp (RNBRunLoopLaunchInferior): Removed unused + variable EXECUTABLE_LENGTH. + (main): Removed unused variable ARG_IDX. + +2008-02-06 Chris Marcellino (cmarcellino@apple.com) and Myke Olson (molson@apple.com) + + * MachProcess.cpp (SBForkChildForPTraceDebugging): Bring up to date with + current SpringBoardServices.framework types and imports. + +2008-02-05 Jason Molenda (jmolenda@apple.com) + + * RNBRemote.cpp (set_logging): Remove the mode=file and filename= + options to the QSetLogging packet. We're only going to support logging + to ASL for now. Logging to a file can still be accomplished by the + -l command line argument. + +2008-02-02 Christopher Friesen (cfriesen@apple.com) + + * Added libXcodeDebugerSupport.dylib target + * XCDebuggerIntrospection.[hc]: Support for Xcode's debugger introspection. + +2008-02-01 Jason Molenda (jmolenda@apple.com) + + * DNBLog.cpp (DNBLogCloseLogFile): New function to close a logfile + at exit. + * DNBLog.h: Prototype. + * test-remotenub.cpp (main): Close the log file before exiting. + +2008-02-01 Jason Molenda (jmolenda@apple.com) + + * RNBRemote.cpp (set_logging): Recognize the "filename=" argument + to the QSetLogging directive. + * DNBLog.cpp (DNBLogGetLogMask): New fun.c + * DNBLog.h: Prototype. + +2008-01-31 Jason Molenda (jmolenda@apple.com) + + * DNBLog.cpp: Add ASL logging as a run-time selectable option. + (DNBLogToASL, DNBLogToFile): Functions to switch between logging to + a file and logging via ASL. + * DNBLog.h: Prototypes. + * RNBRemote.cpp (set_logging): Recognize the "mode=" field to enable + asl logging. Skip unrecognized keys. + +2008-01-31 Greg Clayton (gclayton@apple.com) + + * DNB.cpp (sigchld_handler): Better logging when we get a + SIGCHILD and we are watching for process related logging events. + * test-remotenub.cpp (RNBRunLoopInferiorExecuting): Only reset + events when we still have event bits set. + +2008-01-29 Jason Molenda (jmolenda@apple.com) + + * RNBRemote.h: Add set_logging_mode. + * RNBRemote.cpp (RNBRemote::CreatePacketTable): Recognize + QSetLogging. + +2008-01-29 Jason Molenda (jmolenda@apple.com) + + * RNBRemote.cpp (set_logging): New function to parse the QSetLogging + packet. + (RNBRemote::HandlePacket_Q): Call it. + +2008-01-28 Jason Molenda (jmolenda@apple.com) + + * RNBRemote.h: Minimal packet size is 1024 in our gdb now. + * RNBRemote.cpp: Add the stop_pc value in big-endian order to the + T response packet to make it a little easier to follow where gdb + is stepping. + +2008-01-28 Greg Clayton <gclayton@apple.com> + + * RNBContext.h: Removed m_pid_state from RNBContext class so that + it couldn't get out of sync with the actual process and its accessors + SetProcessState() and GetProcessState(). + * RNBContext.cpp (RNBContext::ProcessStateRunning): Always return the + current state of the process instead of a cached value. + * test-remotenub.cpp (RNBRunLoopLaunchInferior): Remove call to + deprecated RNBContext::SetProcessState(). + (HandleProcessStateChange): Ditto. + +2008-01-24 Greg Clayton (gclayton@apple.com) + + * RNBRemote.cpp (RNBRemote::HandlePacket_q): See if command starts with + "qSymbol" (no trailing "s") and return the empty string. + +2008-01-24 Greg Clayton (gclayton@apple.com) + + * RNBRemote.cpp (RNBRemote::HandlePacket_q): See if command starts with + "qSymbols" and return the empty string. + +2008-01-24 Greg Clayton (gclayton@apple.com) + + * DNBError.h (DNBError::DumpIfError): Removed prototype. + * DNBError.cpp (DNBError::DumpIfError): Removed function. + (DNBError::LogThreadedIfError): Output error as hex. + * MachException.cpp (MachException::Message::Receive): Don't use + DNBError::DumpIfError, now use DNBError::LogThreadedIfError. + * MachProcess.cpp (MachProcess::StartExceptionThread): Ditto. + (MachProcess::Suspend): Ditto. + (MachProcess::SBForkChildForPTraceDebugging): Ditto. + * MachVMMemory.cpp (MachVMMemory::Read): Cleaned up logging + calls. + (MachVMMemory::Write): Ditto. + (MachVMMemory::WriteRegion): Added logging. + * RNBContenxt.cpp (display_thread_info): Removed function. + * RNBRemote.cpp (RNBRemote::GetPacket): Commented out stderr + messages to avoid SpringBoard from killing us. + (RNBRemote::HandlePacket_p): Ditto. + (RNBRemote::HandlePacket_P): Ditto. + (RNBRemote::HandlePacket_c): Ditto. + (RNBRemote::HandlePacket_A): Removed code that was already + * RNBSocket.cpp (RNBSocket::Listen): Commented out stdout + messages to avoid SpringBoard from killing us. + (RNBSocket::ConnectToService): Ditto. + +2008-01-24 Jim Ingham <jingham@apple.com> + + * RNBRemote.cpp (RNBRemote::HandlePacket_q): Reply "" to qSymbols + and qOffsets. + +2008-01-23 Jason Molenda (jmolenda@apple.com) + + * RNBRemote.h: m_noack_mode to RNBRemote class. + * RNBRemote.cpp: Change #ifdef NO_ACKS code blocks + to use m_noack_mode instance variable. + (RNBRemote::HandlePacket_Q): New function to handle + QStartNoAckMode packet and set m_noack_mode appropriately. + * test-remotenub.cpp: Remove NO_ACKS ifdefs. + +2008-01-22 Jason Molenda (jmolenda@apple.com) + + * RNBRemote.cpp (RNBRemote::CreatePacketTable): Recognize + QStartNoAckMode as an unsupported remote protocol request. + * RNBRemote.h: Add start_noack_mode enum entry. + +2008-01-22 Greg Clayton (gclayton@apple.com) + + * DNBLog.h: Removed C++ namespace for DNBLog (changed all DNBLog:: + to DNBLog) so C99 var arg macros can be used to completely disable + all logging and any functions that may be called when making the + variable arguments. + * DNBLog.cpp: Ditto. + * DNB.cpp: Ditto. + * DNBBreakpoint.cpp: Ditto. + * DNBError.cpp: Ditto. + * MacOSX/MachDYLD.cpp: Ditto. + * MacOSX/MachException.cpp: Ditto. + * MacOSX/MachProcess.cpp: Ditto. + * MacOSX/MachThread.cpp: Ditto. + * MacOSX/MachThreadList.cpp: Ditto. + * MacOSX/MachVMMemory.cpp: Ditto. + * MacOSX/MachVMRegion.cpp: Ditto. + * MacOSX/arm/DNBArchImpl.cpp: Ditto. + * MacOSX/ppc/DNBArchImpl.cpp: Ditto. + * PThreadEvent.cpp: Ditto. + * RNBContext.cpp: Ditto. + * RNBRemote.cpp: Ditto. + * RNBSocket.cpp: Ditto. + * test-remotenub.cpp: Ditto. + +2008-01-21 Jason Molenda (jmolenda@apple.com) + + * test-remotenub.cpp: Add NO_SPRINGBOARD for turning off SpringBoard + dependency ala NO_ACKS. + +2008-01-18 Jason Molenda (jmolenda@apple.com) + + * RNBSocket.h (RNBSocket::RNBSocket): Take either a port # or + an already-opened socket, with a boolean to indicate which it is. + * RNBRemote.cpp (RNBRemote::RNBRemote): Ditto. + * RNBRemote.h: Prototype update. + * test-remotenub.cpp: Include lockdown.h. Take --lockdown command + line arg, get the socket from liblockdown.dylib instead of opening + our own socket if it is specified. --lockdown indicates that + the program name/args will be provided via remote protocol instead + of on the command line. + +2008-01-17 Jason Molenda (jmolenda@apple.com) + + * RNBRemote.cpp: Add NO_ACKS #ifdefs around code that computes + the checksums and sends/expects the gdb remote protocol ACK packets. + If NO_ACKS is defined, debugserver will not send or expect acks. + * test-remotenub.cpp (main): Print a different version string + if NO_ACKS is defined. + +2008-01-16 Greg Clayton (gclayton@apple.com) + + * PThreadEvent.cpp: Added this pointer to all logging calls. + +2008-01-16 Greg Clayton (gclayton@apple.com) + + * RNBSocket.cpp (RNBSocket::Connect()): Use TCP so we can try the + TCP_NODELAY socket option. + (RNBSocket::SetSocketOption()): New function. + * RNBSocket.h (RNBSocket::SetSocketOption()): New class function. + +2008-01-14 Jason Molenda (jmolenda@apple.com) + + * RNBRemote.cpp (RNBRemote::HandlePacket_last_signal): When printing + registers, skip over gdb regs which don't map to DNB regs. + +2008-01-14 Jim Ingham <jingham@apple.com> + + * ChangeLog - created. + * RBNContext.h: Added m_arg_vec and accessors. + * RNBContext.cpp (SetProcessID): New function. + * RBNRemote.h: Added packet type to HandlePacket & HandleAsyncPacket + * RNBRemote.cpp (HandlePacket, HandleAsyncPacket): Return type. + (HandlePacket_A): Fix a few bugs. + (HandlePacket_H): Return OK if target is not yet running. + (HandlePacket_q): Return PID of 0 if target is not yet running. + * testremotenub.cpp (RNBRunLoopGetArgsFromRemote): Implement. + (RNBRunLoopLaunchInferior): Fetch arguments from context. + (main) Store arguments in context, call RNBRunLoopGetArgsFromRemote + if appropriate. diff --git a/lldb/tools/debugserver/source/DNB.cpp b/lldb/tools/debugserver/source/DNB.cpp new file mode 100644 index 00000000000..72a111bab2c --- /dev/null +++ b/lldb/tools/debugserver/source/DNB.cpp @@ -0,0 +1,1996 @@ +//===-- DNB.cpp -------------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 3/23/07. +// +//===----------------------------------------------------------------------===// + +#include "DNB.h" +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/resource.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> +#include <sys/sysctl.h> +#include <map> +#include <vector> + +#include "MacOSX/MachProcess.h" +#include "MacOSX/MachTask.h" +#include "CFString.h" +#include "DNBLog.h" +#include "DNBDataRef.h" +#include "DNBThreadResumeActions.h" +#include "DNBTimer.h" + +typedef std::tr1::shared_ptr<MachProcess> MachProcessSP; +typedef std::map<nub_process_t, MachProcessSP> ProcessMap; +typedef ProcessMap::iterator ProcessMapIter; +typedef ProcessMap::const_iterator ProcessMapConstIter; + +static size_t GetAllInfos (std::vector<struct kinfo_proc>& proc_infos); +static size_t GetAllInfosMatchingName (const char *process_name, std::vector<struct kinfo_proc>& matching_proc_infos); + +//---------------------------------------------------------------------- +// A Thread safe singleton to get a process map pointer. +// +// Returns a pointer to the existing process map, or a pointer to a +// newly created process map if CAN_CREATE is non-zero. +//---------------------------------------------------------------------- +static ProcessMap* +GetProcessMap(bool can_create) +{ + static ProcessMap* g_process_map_ptr = NULL; + + if (can_create && g_process_map_ptr == NULL) + { + static pthread_mutex_t g_process_map_mutex = PTHREAD_MUTEX_INITIALIZER; + PTHREAD_MUTEX_LOCKER (locker, &g_process_map_mutex); + if (g_process_map_ptr == NULL) + g_process_map_ptr = new ProcessMap; + } + return g_process_map_ptr; +} + +//---------------------------------------------------------------------- +// Add PID to the shared process pointer map. +// +// Return non-zero value if we succeed in adding the process to the map. +// The only time this should fail is if we run out of memory and can't +// allocate a ProcessMap. +//---------------------------------------------------------------------- +static nub_bool_t +AddProcessToMap (nub_process_t pid, MachProcessSP& procSP) +{ + ProcessMap* process_map = GetProcessMap(true); + if (process_map) + { + process_map->insert(std::make_pair(pid, procSP)); + return true; + } + return false; +} + +//---------------------------------------------------------------------- +// Remove the shared pointer for PID from the process map. +// +// Returns the number of items removed from the process map. +//---------------------------------------------------------------------- +static size_t +RemoveProcessFromMap (nub_process_t pid) +{ + ProcessMap* process_map = GetProcessMap(false); + if (process_map) + { + return process_map->erase(pid); + } + return 0; +} + +//---------------------------------------------------------------------- +// Get the shared pointer for PID from the existing process map. +// +// Returns true if we successfully find a shared pointer to a +// MachProcess object. +//---------------------------------------------------------------------- +static nub_bool_t +GetProcessSP (nub_process_t pid, MachProcessSP& procSP) +{ + ProcessMap* process_map = GetProcessMap(false); + if (process_map != NULL) + { + ProcessMapIter pos = process_map->find(pid); + if (pos != process_map->end()) + { + procSP = pos->second; + return true; + } + } + procSP.reset(); + return false; +} + + +static void * +waitpid_thread (void *arg) +{ + const pid_t pid = (pid_t)(intptr_t)arg; + int status; + while (1) + { + pid_t child_pid = waitpid(pid, &status, 0); + DNBLogThreadedIf(LOG_PROCESS, "waitpid_process_thread (): waitpid (pid = %i, &status, 0) => %i, status = %i, errno = %i", pid, child_pid, status, errno); + + if (child_pid < 0) + { + if (errno == EINTR) + continue; + break; + } + else + { + if (WIFSTOPPED(status)) + { + continue; + } + else// if (WIFEXITED(status) || WIFSIGNALED(status)) + { + DNBLogThreadedIf(LOG_PROCESS, "waitpid_process_thread (): setting exit status for pid = %i to %i", child_pid, status); + DNBProcessSetExitStatus (child_pid, status); + return NULL; + } + } + } + + // We should never exit as long as our child process is alive, so if we + // do something else went wrong and we should exit... + DNBLogThreadedIf(LOG_PROCESS, "waitpid_process_thread (): main loop exited, setting exit status to an invalid value (-1) for pid %i", pid); + DNBProcessSetExitStatus (pid, -1); + return NULL; +} + +static bool +spawn_waitpid_thread (pid_t pid) +{ + pthread_t thread = THREAD_NULL; + ::pthread_create (&thread, NULL, waitpid_thread, (void *)(intptr_t)pid); + if (thread != THREAD_NULL) + { + ::pthread_detach (thread); + return true; + } + return false; +} + +nub_process_t +DNBProcessLaunch (const char *path, + char const *argv[], + const char *envp[], + const char *stdio_path, + nub_launch_flavor_t launch_flavor, + char *err_str, + size_t err_len) +{ + DNBLogThreadedIf(LOG_PROCESS, "%s ( path='%s', argv = %p, envp = %p, launch_flavor = %u, err = %p, err_len = %zu) called...", __FUNCTION__, path, argv, envp, launch_flavor, err_str, err_len); + + if (err_str && err_len > 0) + err_str[0] = '\0'; + struct stat path_stat; + if (::stat(path, &path_stat) == -1) + { + char stat_error[256]; + ::strerror_r (errno, stat_error, sizeof(stat_error)); + snprintf(err_str, err_len, "%s (%s)", stat_error, path); + return INVALID_NUB_PROCESS; + } + + MachProcessSP processSP (new MachProcess); + if (processSP.get()) + { + DNBError launch_err; + pid_t pid = processSP->LaunchForDebug(path, argv, envp, stdio_path, launch_flavor, launch_err); + if (err_str) + { + *err_str = '\0'; + if (launch_err.Fail()) + { + const char *launch_err_str = launch_err.AsString(); + if (launch_err_str) + { + strncpy(err_str, launch_err_str, err_len-1); + err_str[err_len-1] = '\0'; // Make sure the error string is terminated + } + } + } + + DNBLogThreadedIf(LOG_PROCESS, "(DebugNub) new pid is %d...", pid); + + if (pid != INVALID_NUB_PROCESS) + { + // Spawn a thread to reap our child inferior process... + spawn_waitpid_thread (pid); + + if (processSP->Task().TaskPortForProcessID (launch_err) == TASK_NULL) + { + // We failed to get the task for our process ID which is bad. + if (err_str && err_len > 0) + { + if (launch_err.AsString()) + { + ::snprintf (err_str, err_len, "failed to get the task for process %i (%s)", pid, launch_err.AsString()); + } + else + { + ::snprintf (err_str, err_len, "failed to get the task for process %i", pid); + } + } + } + else + { + assert(AddProcessToMap(pid, processSP)); + return pid; + } + } + } + return INVALID_NUB_PROCESS; +} + +nub_process_t +DNBProcessAttachByName (const char *name, struct timespec *timeout, char *err_str, size_t err_len) +{ + if (err_str && err_len > 0) + err_str[0] = '\0'; + std::vector<struct kinfo_proc> matching_proc_infos; + size_t num_matching_proc_infos = GetAllInfosMatchingName(name, matching_proc_infos); + if (num_matching_proc_infos == 0) + { + DNBLogError ("error: no processes match '%s'\n", name); + return INVALID_NUB_PROCESS; + } + else if (num_matching_proc_infos > 1) + { + DNBLogError ("error: %u processes match '%s':\n", num_matching_proc_infos, name); + size_t i; + for (i=0; i<num_matching_proc_infos; ++i) + DNBLogError ("%6u - %s\n", matching_proc_infos[i].kp_proc.p_pid, matching_proc_infos[i].kp_proc.p_comm); + return INVALID_NUB_PROCESS; + } + else + { + return DNBProcessAttach (matching_proc_infos[0].kp_proc.p_pid, timeout, err_str, err_len); + } +} + +nub_process_t +DNBProcessAttach (nub_process_t attach_pid, struct timespec *timeout, char *err_str, size_t err_len) +{ + if (err_str && err_len > 0) + err_str[0] = '\0'; + + pid_t pid; + MachProcessSP processSP(new MachProcess); + if (processSP.get()) + { + DNBLogThreadedIf(LOG_PROCESS, "(DebugNub) attaching to pid %d...", attach_pid); + pid = processSP->AttachForDebug (attach_pid, err_str, err_len); + + if (pid != INVALID_NUB_PROCESS) + { + assert(AddProcessToMap(pid, processSP)); + spawn_waitpid_thread(pid); + } + } + + while (pid != INVALID_NUB_PROCESS) + { + // Wait for process to start up and hit entry point + DNBLogThreadedIf (LOG_PROCESS, "%s DNBProcessWaitForEvent (%4.4x, eEventProcessRunningStateChanged " + "| eEventProcessStoppedStateChanged, true, INFINITE)...", + __FUNCTION__, pid); + nub_event_t set_events = DNBProcessWaitForEvents (pid, + eEventProcessRunningStateChanged | eEventProcessStoppedStateChanged, + true, timeout); + DNBLogThreadedIf (LOG_PROCESS, "%s DNBProcessWaitForEvent (%4.4x, eEventProcessRunningStateChanged " + "| eEventProcessStoppedStateChanged, true, INFINITE) => 0x%8.8x", + __FUNCTION__, pid, set_events); + + if (set_events == 0) + { + if (err_str && err_len > 0) + snprintf(err_str, err_len, "operation timed out"); + pid = INVALID_NUB_PROCESS; + } + else + { + if (set_events & (eEventProcessRunningStateChanged | eEventProcessStoppedStateChanged)) + { + nub_state_t pid_state = DNBProcessGetState (pid); + DNBLogThreadedIf (LOG_PROCESS, "%s process %4.4x state changed (eEventProcessStateChanged): %s", + __FUNCTION__, pid, DNBStateAsString(pid_state)); + + switch (pid_state) + { + default: + case eStateInvalid: + case eStateUnloaded: + case eStateAttaching: + case eStateLaunching: + case eStateSuspended: + break; // Ignore + + case eStateRunning: + case eStateStepping: + // Still waiting to stop at entry point... + break; + + case eStateStopped: + case eStateCrashed: + return pid; + case eStateDetached: + case eStateExited: + if (err_str && err_len > 0) + snprintf(err_str, err_len, "process exited"); + return INVALID_NUB_PROCESS; + } + } + + DNBProcessResetEvents(pid, set_events); + } + } + + return INVALID_NUB_PROCESS; +} + +static size_t +GetAllInfos(std::vector<struct kinfo_proc>& proc_infos) +{ + size_t size; + int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL }; + u_int namelen = sizeof(name)/sizeof(int); + int err; + + // Try to find out how many processes are around so we can + // size the buffer appropriately. sysctl's man page specifically suggests + // this approach, and says it returns a bit larger size than needed to + // handle any new processes created between then and now. + + err = ::sysctl (name, namelen, NULL, &size, NULL, 0); + + if ((err < 0) && (err != ENOMEM)) + { + proc_infos.clear(); + perror("sysctl (mib, miblen, NULL, &num_processes, NULL, 0)"); + return 0; + } + + + // Increase the size of the buffer by a few processes in case more have + // been spawned + proc_infos.resize (size / sizeof(struct kinfo_proc)); + size = proc_infos.size() * sizeof(struct kinfo_proc); // Make sure we don't exceed our resize... + err = ::sysctl (name, namelen, &proc_infos[0], &size, NULL, 0); + if (err < 0) + { + proc_infos.clear(); + return 0; + } + + // Trim down our array to fit what we actually got back + proc_infos.resize(size / sizeof(struct kinfo_proc)); + return proc_infos.size(); +} + + +static size_t +GetAllInfosMatchingName(const char *full_process_name, std::vector<struct kinfo_proc>& matching_proc_infos) +{ + + matching_proc_infos.clear(); + if (full_process_name && full_process_name[0]) + { + // We only get the process name, not the full path, from the proc_info. So just take the + // base name of the process name... + const char *process_name; + process_name = strrchr (full_process_name, '/'); + if (process_name == NULL) + process_name = full_process_name; + else + process_name++; + + std::vector<struct kinfo_proc> proc_infos; + const size_t num_proc_infos = GetAllInfos(proc_infos); + if (num_proc_infos > 0) + { + uint32_t i; + for (i=0; i<num_proc_infos; i++) + { + // Skip zombie processes and processes with unset status + if (proc_infos[i].kp_proc.p_stat == 0 || proc_infos[i].kp_proc.p_stat == SZOMB) + continue; + + // Check for process by name. We only check the first MAXCOMLEN + // chars as that is all that kp_proc.p_comm holds. + if (::strncasecmp(proc_infos[i].kp_proc.p_comm, process_name, MAXCOMLEN) == 0) + { + // We found a matching process, add it to our list + matching_proc_infos.push_back(proc_infos[i]); + } + } + } + } + // return the newly added matches. + return matching_proc_infos.size(); +} + +nub_process_t +DNBProcessAttachWait (const char *waitfor_process_name, nub_launch_flavor_t launch_flavor, + struct timespec *timeout_abstime, useconds_t waitfor_interval, + char *err_str, size_t err_len, + DNBShouldCancelCallback should_cancel_callback, + void *callback_data) +{ + DNBError prepare_error; + std::vector<struct kinfo_proc> exclude_proc_infos; + size_t num_exclude_proc_infos; + + // If the PrepareForAttach returns a valid token, use MachProcess to check + // for the process, otherwise scan the process table. + + const void *attach_token = MachProcess::PrepareForAttach (waitfor_process_name, launch_flavor, true, prepare_error); + + if (prepare_error.Fail()) + { + DNBLogError ("Error in PrepareForAttach: %s", prepare_error.AsString()); + return INVALID_NUB_PROCESS; + } + + if (attach_token == NULL) + num_exclude_proc_infos = GetAllInfosMatchingName (waitfor_process_name, exclude_proc_infos); + + DNBLogThreadedIf (LOG_PROCESS, "Waiting for '%s' to appear...\n", waitfor_process_name); + + // Loop and try to find the process by name + nub_process_t waitfor_pid = INVALID_NUB_PROCESS; + + while (waitfor_pid == INVALID_NUB_PROCESS) + { + if (attach_token != NULL) + { + nub_process_t pid; + pid = MachProcess::CheckForProcess(attach_token); + if (pid != INVALID_NUB_PROCESS) + { + waitfor_pid = pid; + break; + } + } + else + { + + // Get the current process list, and check for matches that + // aren't in our original list. If anyone wants to attach + // to an existing process by name, they should do it with + // --attach=PROCNAME. Else we will wait for the first matching + // process that wasn't in our exclusion list. + std::vector<struct kinfo_proc> proc_infos; + const size_t num_proc_infos = GetAllInfosMatchingName (waitfor_process_name, proc_infos); + for (size_t i=0; i<num_proc_infos; i++) + { + nub_process_t curr_pid = proc_infos[i].kp_proc.p_pid; + for (size_t j=0; j<num_exclude_proc_infos; j++) + { + if (curr_pid == exclude_proc_infos[j].kp_proc.p_pid) + { + // This process was in our exclusion list, don't use it. + curr_pid = INVALID_NUB_PROCESS; + break; + } + } + + // If we didn't find CURR_PID in our exclusion list, then use it. + if (curr_pid != INVALID_NUB_PROCESS) + { + // We found our process! + waitfor_pid = curr_pid; + break; + } + } + } + + // If we haven't found our process yet, check for a timeout + // and then sleep for a bit until we poll again. + if (waitfor_pid == INVALID_NUB_PROCESS) + { + if (timeout_abstime != NULL) + { + // Check to see if we have a waitfor-duration option that + // has timed out? + if (DNBTimer::TimeOfDayLaterThan(*timeout_abstime)) + { + if (err_str && err_len > 0) + snprintf(err_str, err_len, "operation timed out"); + DNBLogError ("error: waiting for process '%s' timed out.\n", waitfor_process_name); + return INVALID_NUB_PROCESS; + } + } + + // Call the should cancel callback as well... + + if (should_cancel_callback != NULL + && should_cancel_callback (callback_data)) + { + DNBLogThreadedIf (LOG_PROCESS, "DNBProcessAttachWait cancelled by should_cancel callback."); + waitfor_pid = INVALID_NUB_PROCESS; + break; + } + + ::usleep (waitfor_interval); // Sleep for WAITFOR_INTERVAL, then poll again + } + } + + if (waitfor_pid != INVALID_NUB_PROCESS) + { + DNBLogThreadedIf (LOG_PROCESS, "Attaching to %s with pid %i...\n", waitfor_process_name, waitfor_pid); + waitfor_pid = DNBProcessAttach (waitfor_pid, timeout_abstime, err_str, err_len); + } + + bool success = waitfor_pid != INVALID_NUB_PROCESS; + MachProcess::CleanupAfterAttach (attach_token, success, prepare_error); + + return waitfor_pid; +} + +nub_bool_t +DNBProcessDetach (nub_process_t pid) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + { + return procSP->Detach(); + } + return false; +} + +nub_bool_t +DNBProcessKill (nub_process_t pid) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + { + return procSP->Kill (); + } + return false; +} + +nub_bool_t +DNBProcessSignal (nub_process_t pid, int signal) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + { + return procSP->Signal (signal); + } + return false; +} + + +nub_bool_t +DNBProcessIsAlive (nub_process_t pid) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + { + return MachTask::IsValid (procSP->Task().TaskPort()); + } + return eStateInvalid; +} + +//---------------------------------------------------------------------- +// Process and Thread state information +//---------------------------------------------------------------------- +nub_state_t +DNBProcessGetState (nub_process_t pid) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + { + return procSP->GetState(); + } + return eStateInvalid; +} + +//---------------------------------------------------------------------- +// Process and Thread state information +//---------------------------------------------------------------------- +nub_bool_t +DNBProcessGetExitStatus (nub_process_t pid, int* status) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + { + return procSP->GetExitStatus(status); + } + return false; +} + +nub_bool_t +DNBProcessSetExitStatus (nub_process_t pid, int status) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + { + procSP->SetExitStatus(status); + return true; + } + return false; +} + + +const char * +DNBThreadGetName (nub_process_t pid, nub_thread_t tid) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + return procSP->ThreadGetName(tid); + return NULL; +} + + +nub_bool_t +DNBThreadGetIdentifierInfo (nub_process_t pid, nub_thread_t tid, thread_identifier_info_data_t *ident_info) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + return procSP->GetThreadList().GetIdentifierInfo(tid, ident_info); + return false; +} + +nub_state_t +DNBThreadGetState (nub_process_t pid, nub_thread_t tid) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + { + return procSP->ThreadGetState(tid); + } + return eStateInvalid; +} + +const char * +DNBStateAsString(nub_state_t state) +{ + switch (state) + { + case eStateUnloaded: return "Unloaded"; + case eStateAttaching: return "Attaching"; + case eStateLaunching: return "Launching"; + case eStateStopped: return "Stopped"; + case eStateRunning: return "Running"; + case eStateStepping: return "Stepping"; + case eStateCrashed: return "Crashed"; + case eStateDetached: return "Detached"; + case eStateExited: return "Exited"; + case eStateSuspended: return "Suspended"; + } + return "nub_state_t ???"; +} + +const char * +DNBProcessGetExecutablePath (nub_process_t pid) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + { + return procSP->Path(); + } + return NULL; +} + +nub_size_t +DNBProcessGetArgumentCount (nub_process_t pid) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + { + return procSP->ArgumentCount(); + } + return 0; +} + +const char * +DNBProcessGetArgumentAtIndex (nub_process_t pid, nub_size_t idx) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + { + return procSP->ArgumentAtIndex (idx); + } + return NULL; +} + + +//---------------------------------------------------------------------- +// Execution control +//---------------------------------------------------------------------- +nub_bool_t +DNBProcessResume (nub_process_t pid, const DNBThreadResumeAction *actions, size_t num_actions) +{ + DNBLogThreadedIf(LOG_PROCESS, "%s(pid = %4.4x)", __FUNCTION__, pid); + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + { + DNBThreadResumeActions thread_actions (actions, num_actions); + + // Below we add a default thread plan just in case one wasn't + // provided so all threads always know what they were supposed to do + if (thread_actions.IsEmpty()) + { + // No thread plans were given, so the default it to run all threads + thread_actions.SetDefaultThreadActionIfNeeded (eStateRunning, 0); + } + else + { + // Some thread plans were given which means anything that wasn't + // specified should remain stopped. + thread_actions.SetDefaultThreadActionIfNeeded (eStateStopped, 0); + } + return procSP->Resume (thread_actions); + } + return false; +} + +nub_bool_t +DNBProcessHalt (nub_process_t pid) +{ + DNBLogThreadedIf(LOG_PROCESS, "%s(pid = %4.4x)", __FUNCTION__, pid); + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + return procSP->Signal (SIGSTOP); + return false; +} +// +//nub_bool_t +//DNBThreadResume (nub_process_t pid, nub_thread_t tid, nub_bool_t step) +//{ +// DNBLogThreadedIf(LOG_THREAD, "%s(pid = %4.4x, tid = %4.4x, step = %u)", __FUNCTION__, pid, tid, (uint32_t)step); +// MachProcessSP procSP; +// if (GetProcessSP (pid, procSP)) +// { +// return procSP->Resume(tid, step, 0); +// } +// return false; +//} +// +//nub_bool_t +//DNBThreadResumeWithSignal (nub_process_t pid, nub_thread_t tid, nub_bool_t step, int signal) +//{ +// DNBLogThreadedIf(LOG_THREAD, "%s(pid = %4.4x, tid = %4.4x, step = %u, signal = %i)", __FUNCTION__, pid, tid, (uint32_t)step, signal); +// MachProcessSP procSP; +// if (GetProcessSP (pid, procSP)) +// { +// return procSP->Resume(tid, step, signal); +// } +// return false; +//} + +nub_event_t +DNBProcessWaitForEvents (nub_process_t pid, nub_event_t event_mask, bool wait_for_set, struct timespec* timeout) +{ + nub_event_t result = 0; + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + { + if (wait_for_set) + result = procSP->Events().WaitForSetEvents(event_mask, timeout); + else + result = procSP->Events().WaitForEventsToReset(event_mask, timeout); + } + return result; +} + +void +DNBProcessResetEvents (nub_process_t pid, nub_event_t event_mask) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + procSP->Events().ResetEvents(event_mask); +} + +void +DNBProcessInterruptEvents (nub_process_t pid) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + procSP->Events().SetEvents(eEventProcessAsyncInterrupt); +} + + +// Breakpoints +nub_break_t +DNBBreakpointSet (nub_process_t pid, nub_addr_t addr, nub_size_t size, nub_bool_t hardware) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + { + return procSP->CreateBreakpoint(addr, size, hardware, THREAD_NULL); + } + return INVALID_NUB_BREAK_ID; +} + +nub_bool_t +DNBBreakpointClear (nub_process_t pid, nub_break_t breakID) +{ + if (NUB_BREAK_ID_IS_VALID(breakID)) + { + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + { + return procSP->DisableBreakpoint(breakID, true); + } + } + return false; // Failed +} + +nub_ssize_t +DNBBreakpointGetHitCount (nub_process_t pid, nub_break_t breakID) +{ + if (NUB_BREAK_ID_IS_VALID(breakID)) + { + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + { + DNBBreakpoint *bp = procSP->Breakpoints().FindByID(breakID); + if (bp) + return bp->GetHitCount(); + } + } + return 0; +} + +nub_ssize_t +DNBBreakpointGetIgnoreCount (nub_process_t pid, nub_break_t breakID) +{ + if (NUB_BREAK_ID_IS_VALID(breakID)) + { + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + { + DNBBreakpoint *bp = procSP->Breakpoints().FindByID(breakID); + if (bp) + return bp->GetIgnoreCount(); + } + } + return 0; +} + +nub_bool_t +DNBBreakpointSetIgnoreCount (nub_process_t pid, nub_break_t breakID, nub_size_t ignore_count) +{ + if (NUB_BREAK_ID_IS_VALID(breakID)) + { + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + { + DNBBreakpoint *bp = procSP->Breakpoints().FindByID(breakID); + if (bp) + { + bp->SetIgnoreCount(ignore_count); + return true; + } + } + } + return false; +} + +// Set the callback function for a given breakpoint. The callback function will +// get called as soon as the breakpoint is hit. The function will be called +// with the process ID, thread ID, breakpoint ID and the baton, and can return +// +nub_bool_t +DNBBreakpointSetCallback (nub_process_t pid, nub_break_t breakID, DNBCallbackBreakpointHit callback, void *baton) +{ + if (NUB_BREAK_ID_IS_VALID(breakID)) + { + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + { + DNBBreakpoint *bp = procSP->Breakpoints().FindByID(breakID); + if (bp) + { + bp->SetCallback(callback, baton); + return true; + } + } + } + return false; +} + +//---------------------------------------------------------------------- +// Dump the breakpoints stats for process PID for a breakpoint by ID. +//---------------------------------------------------------------------- +void +DNBBreakpointPrint (nub_process_t pid, nub_break_t breakID) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + procSP->DumpBreakpoint(breakID); +} + +//---------------------------------------------------------------------- +// Watchpoints +//---------------------------------------------------------------------- +nub_watch_t +DNBWatchpointSet (nub_process_t pid, nub_addr_t addr, nub_size_t size, uint32_t watch_flags, nub_bool_t hardware) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + { + return procSP->CreateWatchpoint(addr, size, watch_flags, hardware, THREAD_NULL); + } + return INVALID_NUB_BREAK_ID; +} + +nub_bool_t +DNBWatchpointClear (nub_process_t pid, nub_watch_t watchID) +{ + if (NUB_BREAK_ID_IS_VALID(watchID)) + { + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + { + return procSP->DisableWatchpoint(watchID, true); + } + } + return false; // Failed +} + +nub_ssize_t +DNBWatchpointGetHitCount (nub_process_t pid, nub_watch_t watchID) +{ + if (NUB_BREAK_ID_IS_VALID(watchID)) + { + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + { + DNBBreakpoint *bp = procSP->Watchpoints().FindByID(watchID); + if (bp) + return bp->GetHitCount(); + } + } + return 0; +} + +nub_ssize_t +DNBWatchpointGetIgnoreCount (nub_process_t pid, nub_watch_t watchID) +{ + if (NUB_BREAK_ID_IS_VALID(watchID)) + { + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + { + DNBBreakpoint *bp = procSP->Watchpoints().FindByID(watchID); + if (bp) + return bp->GetIgnoreCount(); + } + } + return 0; +} + +nub_bool_t +DNBWatchpointSetIgnoreCount (nub_process_t pid, nub_watch_t watchID, nub_size_t ignore_count) +{ + if (NUB_BREAK_ID_IS_VALID(watchID)) + { + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + { + DNBBreakpoint *bp = procSP->Watchpoints().FindByID(watchID); + if (bp) + { + bp->SetIgnoreCount(ignore_count); + return true; + } + } + } + return false; +} + +// Set the callback function for a given watchpoint. The callback function will +// get called as soon as the watchpoint is hit. The function will be called +// with the process ID, thread ID, watchpoint ID and the baton, and can return +// +nub_bool_t +DNBWatchpointSetCallback (nub_process_t pid, nub_watch_t watchID, DNBCallbackBreakpointHit callback, void *baton) +{ + if (NUB_BREAK_ID_IS_VALID(watchID)) + { + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + { + DNBBreakpoint *bp = procSP->Watchpoints().FindByID(watchID); + if (bp) + { + bp->SetCallback(callback, baton); + return true; + } + } + } + return false; +} + +//---------------------------------------------------------------------- +// Dump the watchpoints stats for process PID for a watchpoint by ID. +//---------------------------------------------------------------------- +void +DNBWatchpointPrint (nub_process_t pid, nub_watch_t watchID) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + procSP->DumpWatchpoint(watchID); +} + +//---------------------------------------------------------------------- +// Read memory in the address space of process PID. This call will take +// care of setting and restoring permissions and breaking up the memory +// read into multiple chunks as required. +// +// RETURNS: number of bytes actually read +//---------------------------------------------------------------------- +nub_size_t +DNBProcessMemoryRead (nub_process_t pid, nub_addr_t addr, nub_size_t size, void *buf) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + return procSP->ReadMemory(addr, size, buf); + return 0; +} + +//---------------------------------------------------------------------- +// Write memory to the address space of process PID. This call will take +// care of setting and restoring permissions and breaking up the memory +// write into multiple chunks as required. +// +// RETURNS: number of bytes actually written +//---------------------------------------------------------------------- +nub_size_t +DNBProcessMemoryWrite (nub_process_t pid, nub_addr_t addr, nub_size_t size, const void *buf) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + return procSP->WriteMemory(addr, size, buf); + return 0; +} + +nub_addr_t +DNBProcessMemoryAllocate (nub_process_t pid, nub_size_t size, uint32_t permissions) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + return procSP->Task().AllocateMemory (size, permissions); + return 0; +} + +nub_bool_t +DNBProcessMemoryDeallocate (nub_process_t pid, nub_addr_t addr) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + return procSP->Task().DeallocateMemory (addr); + return 0; +} + + +//---------------------------------------------------------------------- +// Formatted output that uses memory and registers from process and +// thread in place of arguments. +//---------------------------------------------------------------------- +nub_size_t +DNBPrintf (nub_process_t pid, nub_thread_t tid, nub_addr_t base_addr, FILE *file, const char *format) +{ + if (file == NULL) + return 0; + enum printf_flags + { + alternate_form = (1 << 0), + zero_padding = (1 << 1), + negative_field_width = (1 << 2), + blank_space = (1 << 3), + show_sign = (1 << 4), + show_thousands_separator= (1 << 5), + }; + + enum printf_length_modifiers + { + length_mod_h = (1 << 0), + length_mod_hh = (1 << 1), + length_mod_l = (1 << 2), + length_mod_ll = (1 << 3), + length_mod_L = (1 << 4), + length_mod_j = (1 << 5), + length_mod_t = (1 << 6), + length_mod_z = (1 << 7), + length_mod_q = (1 << 8), + }; + + nub_addr_t addr = base_addr; + char *end_format = (char*)format + strlen(format); + char *end = NULL; // For strtoXXXX calls; + std::basic_string<uint8_t> buf; + nub_size_t total_bytes_read = 0; + DNBDataRef data; + const char *f; + for (f = format; *f != '\0' && f < end_format; f++) + { + char ch = *f; + switch (ch) + { + case '%': + { + f++; // Skip the '%' character + int min_field_width = 0; + int precision = 0; + uint32_t flags = 0; + uint32_t length_modifiers = 0; + uint32_t byte_size = 0; + uint32_t actual_byte_size = 0; + bool is_string = false; + bool is_register = false; + DNBRegisterValue register_value; + int64_t register_offset = 0; + nub_addr_t register_addr = INVALID_NUB_ADDRESS; + + // Create the format string to use for this conversion specification + // so we can remove and mprintf specific flags and formatters. + std::string fprintf_format("%"); + + // Decode any flags + switch (*f) + { + case '#': fprintf_format += *f++; flags |= alternate_form; break; + case '0': fprintf_format += *f++; flags |= zero_padding; break; + case '-': fprintf_format += *f++; flags |= negative_field_width; break; + case ' ': fprintf_format += *f++; flags |= blank_space; break; + case '+': fprintf_format += *f++; flags |= show_sign; break; + case ',': fprintf_format += *f++; flags |= show_thousands_separator;break; + case '{': + case '[': + { + // We have a register name specification that can take two forms: + // ${regname} or ${regname+offset} + // The action is to read the register value and add the signed offset + // (if any) and use that as the value to format. + // $[regname] or $[regname+offset] + // The action is to read the register value and add the signed offset + // (if any) and use the result as an address to dereference. The size + // of what is dereferenced is specified by the actual byte size that + // follows the minimum field width and precision (see comments below). + switch (*f) + { + case '{': + case '[': + { + char open_scope_ch = *f; + f++; + const char *reg_name = f; + size_t reg_name_length = strcspn(f, "+-}]"); + if (reg_name_length > 0) + { + std::string register_name(reg_name, reg_name_length); + f += reg_name_length; + register_offset = strtoll(f, &end, 0); + if (f < end) + f = end; + if ((open_scope_ch == '{' && *f != '}') || (open_scope_ch == '[' && *f != ']')) + { + fprintf(file, "error: Invalid register format string. Valid formats are %%{regname} or %%{regname+offset}, %%[regname] or %%[regname+offset]\n"); + return total_bytes_read; + } + else + { + f++; + if (DNBThreadGetRegisterValueByName(pid, tid, REGISTER_SET_ALL, register_name.c_str(), ®ister_value)) + { + // Set the address to dereference using the register value plus the offset + switch (register_value.info.size) + { + default: + case 0: + fprintf (file, "error: unsupported register size of %u.\n", register_value.info.size); + return total_bytes_read; + + case 1: register_addr = register_value.value.uint8 + register_offset; break; + case 2: register_addr = register_value.value.uint16 + register_offset; break; + case 4: register_addr = register_value.value.uint32 + register_offset; break; + case 8: register_addr = register_value.value.uint64 + register_offset; break; + case 16: + if (open_scope_ch == '[') + { + fprintf (file, "error: register size (%u) too large for address.\n", register_value.info.size); + return total_bytes_read; + } + break; + } + + if (open_scope_ch == '{') + { + byte_size = register_value.info.size; + is_register = true; // value is in a register + + } + else + { + addr = register_addr; // Use register value and offset as the address + } + } + else + { + fprintf(file, "error: unable to read register '%s' for process %#.4x and thread %#.4x\n", register_name.c_str(), pid, tid); + return total_bytes_read; + } + } + } + } + break; + + default: + fprintf(file, "error: %%$ must be followed by (regname + n) or [regname + n]\n"); + return total_bytes_read; + } + } + break; + } + + // Check for a minimum field width + if (isdigit(*f)) + { + min_field_width = strtoul(f, &end, 10); + if (end > f) + { + fprintf_format.append(f, end - f); + f = end; + } + } + + + // Check for a precision + if (*f == '.') + { + f++; + if (isdigit(*f)) + { + fprintf_format += '.'; + precision = strtoul(f, &end, 10); + if (end > f) + { + fprintf_format.append(f, end - f); + f = end; + } + } + } + + + // mprintf specific: read the optional actual byte size (abs) + // after the standard minimum field width (mfw) and precision (prec). + // Standard printf calls you can have "mfw.prec" or ".prec", but + // mprintf can have "mfw.prec.abs", ".prec.abs" or "..abs". This is nice + // for strings that may be in a fixed size buffer, but may not use all bytes + // in that buffer for printable characters. + if (*f == '.') + { + f++; + actual_byte_size = strtoul(f, &end, 10); + if (end > f) + { + byte_size = actual_byte_size; + f = end; + } + } + + // Decode the length modifiers + switch (*f) + { + case 'h': // h and hh length modifiers + fprintf_format += *f++; + length_modifiers |= length_mod_h; + if (*f == 'h') + { + fprintf_format += *f++; + length_modifiers |= length_mod_hh; + } + break; + + case 'l': // l and ll length modifiers + fprintf_format += *f++; + length_modifiers |= length_mod_l; + if (*f == 'h') + { + fprintf_format += *f++; + length_modifiers |= length_mod_ll; + } + break; + + case 'L': fprintf_format += *f++; length_modifiers |= length_mod_L; break; + case 'j': fprintf_format += *f++; length_modifiers |= length_mod_j; break; + case 't': fprintf_format += *f++; length_modifiers |= length_mod_t; break; + case 'z': fprintf_format += *f++; length_modifiers |= length_mod_z; break; + case 'q': fprintf_format += *f++; length_modifiers |= length_mod_q; break; + } + + // Decode the conversion specifier + switch (*f) + { + case '_': + // mprintf specific format items + { + ++f; // Skip the '_' character + switch (*f) + { + case 'a': // Print the current address + ++f; + fprintf_format += "ll"; + fprintf_format += *f; // actual format to show address with folows the 'a' ("%_ax") + fprintf (file, fprintf_format.c_str(), addr); + break; + case 'o': // offset from base address + ++f; + fprintf_format += "ll"; + fprintf_format += *f; // actual format to show address with folows the 'a' ("%_ox") + fprintf(file, fprintf_format.c_str(), addr - base_addr); + break; + default: + fprintf (file, "error: unsupported mprintf specific format character '%c'.\n", *f); + break; + } + continue; + } + break; + + case 'D': + case 'O': + case 'U': + fprintf_format += *f; + if (byte_size == 0) + byte_size = sizeof(long int); + break; + + case 'd': + case 'i': + case 'o': + case 'u': + case 'x': + case 'X': + fprintf_format += *f; + if (byte_size == 0) + { + if (length_modifiers & length_mod_hh) + byte_size = sizeof(char); + else if (length_modifiers & length_mod_h) + byte_size = sizeof(short); + if (length_modifiers & length_mod_ll) + byte_size = sizeof(long long); + else if (length_modifiers & length_mod_l) + byte_size = sizeof(long); + else + byte_size = sizeof(int); + } + break; + + case 'a': + case 'A': + case 'f': + case 'F': + case 'e': + case 'E': + case 'g': + case 'G': + fprintf_format += *f; + if (byte_size == 0) + { + if (length_modifiers & length_mod_L) + byte_size = sizeof(long double); + else + byte_size = sizeof(double); + } + break; + + case 'c': + if ((length_modifiers & length_mod_l) == 0) + { + fprintf_format += *f; + if (byte_size == 0) + byte_size = sizeof(char); + break; + } + // Fall through to 'C' modifier below... + + case 'C': + fprintf_format += *f; + if (byte_size == 0) + byte_size = sizeof(wchar_t); + break; + + case 's': + fprintf_format += *f; + if (is_register || byte_size == 0) + is_string = 1; + break; + + case 'p': + fprintf_format += *f; + if (byte_size == 0) + byte_size = sizeof(void*); + break; + } + + if (is_string) + { + std::string mem_string; + const size_t string_buf_len = 4; + char string_buf[string_buf_len+1]; + char *string_buf_end = string_buf + string_buf_len; + string_buf[string_buf_len] = '\0'; + nub_size_t bytes_read; + nub_addr_t str_addr = is_register ? register_addr : addr; + while ((bytes_read = DNBProcessMemoryRead(pid, str_addr, string_buf_len, &string_buf[0])) > 0) + { + // Did we get a NULL termination character yet? + if (strchr(string_buf, '\0') == string_buf_end) + { + // no NULL terminator yet, append as a std::string + mem_string.append(string_buf, string_buf_len); + str_addr += string_buf_len; + } + else + { + // yep + break; + } + } + // Append as a C-string so we don't get the extra NULL + // characters in the temp buffer (since it was resized) + mem_string += string_buf; + size_t mem_string_len = mem_string.size() + 1; + fprintf(file, fprintf_format.c_str(), mem_string.c_str()); + if (mem_string_len > 0) + { + if (!is_register) + { + addr += mem_string_len; + total_bytes_read += mem_string_len; + } + } + else + return total_bytes_read; + } + else + if (byte_size > 0) + { + buf.resize(byte_size); + nub_size_t bytes_read = 0; + if (is_register) + bytes_read = register_value.info.size; + else + bytes_read = DNBProcessMemoryRead(pid, addr, buf.size(), &buf[0]); + if (bytes_read > 0) + { + if (!is_register) + total_bytes_read += bytes_read; + + if (bytes_read == byte_size) + { + switch (*f) + { + case 'd': + case 'i': + case 'o': + case 'u': + case 'X': + case 'x': + case 'a': + case 'A': + case 'f': + case 'F': + case 'e': + case 'E': + case 'g': + case 'G': + case 'p': + case 'c': + case 'C': + { + if (is_register) + data.SetData(®ister_value.value.v_uint8[0], register_value.info.size); + else + data.SetData(&buf[0], bytes_read); + DNBDataRef::offset_t data_offset = 0; + if (byte_size <= 4) + { + uint32_t u32 = data.GetMax32(&data_offset, byte_size); + // Show the actual byte width when displaying hex + fprintf(file, fprintf_format.c_str(), u32); + } + else if (byte_size <= 8) + { + uint64_t u64 = data.GetMax64(&data_offset, byte_size); + // Show the actual byte width when displaying hex + fprintf(file, fprintf_format.c_str(), u64); + } + else + { + fprintf(file, "error: integer size not supported, must be 8 bytes or less (%u bytes).\n", byte_size); + } + if (!is_register) + addr += byte_size; + } + break; + + case 's': + fprintf(file, fprintf_format.c_str(), buf.c_str()); + addr += byte_size; + break; + + default: + fprintf(file, "error: unsupported conversion specifier '%c'.\n", *f); + break; + } + } + } + } + else + return total_bytes_read; + } + break; + + case '\\': + { + f++; + switch (*f) + { + case 'e': ch = '\e'; break; + case 'a': ch = '\a'; break; + case 'b': ch = '\b'; break; + case 'f': ch = '\f'; break; + case 'n': ch = '\n'; break; + case 'r': ch = '\r'; break; + case 't': ch = '\t'; break; + case 'v': ch = '\v'; break; + case '\'': ch = '\''; break; + case '\\': ch = '\\'; break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + ch = strtoul(f, &end, 8); + f = end; + break; + default: + ch = *f; + break; + } + fputc(ch, file); + } + break; + + default: + fputc(ch, file); + break; + } + } + return total_bytes_read; +} + + +//---------------------------------------------------------------------- +// Get the number of threads for the specified process. +//---------------------------------------------------------------------- +nub_size_t +DNBProcessGetNumThreads (nub_process_t pid) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + return procSP->GetNumThreads(); + return 0; +} + +//---------------------------------------------------------------------- +// Get the thread ID of the current thread. +//---------------------------------------------------------------------- +nub_thread_t +DNBProcessGetCurrentThread (nub_process_t pid) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + return procSP->GetCurrentThread(); + return 0; +} + +//---------------------------------------------------------------------- +// Change the current thread. +//---------------------------------------------------------------------- +nub_thread_t +DNBProcessSetCurrentThread (nub_process_t pid, nub_thread_t tid) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + return procSP->SetCurrentThread (tid); + return INVALID_NUB_THREAD; +} + + +//---------------------------------------------------------------------- +// Dump a string describing a thread's stop reason to the specified file +// handle +//---------------------------------------------------------------------- +nub_bool_t +DNBThreadGetStopReason (nub_process_t pid, nub_thread_t tid, struct DNBThreadStopInfo *stop_info) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + return procSP->GetThreadStoppedReason (tid, stop_info); + return false; +} + +//---------------------------------------------------------------------- +// Return string description for the specified thread. +// +// RETURNS: NULL if the thread isn't valid, else a NULL terminated C +// string from a static buffer that must be copied prior to subsequent +// calls. +//---------------------------------------------------------------------- +const char * +DNBThreadGetInfo (nub_process_t pid, nub_thread_t tid) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + return procSP->GetThreadInfo (tid); + return NULL; +} + +//---------------------------------------------------------------------- +// Get the thread ID given a thread index. +//---------------------------------------------------------------------- +nub_thread_t +DNBProcessGetThreadAtIndex (nub_process_t pid, size_t thread_idx) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + return procSP->GetThreadAtIndex (thread_idx); + return INVALID_NUB_THREAD; +} + +nub_addr_t +DNBProcessGetSharedLibraryInfoAddress (nub_process_t pid) +{ + MachProcessSP procSP; + DNBError err; + if (GetProcessSP (pid, procSP)) + return procSP->Task().GetDYLDAllImageInfosAddress (err); + return INVALID_NUB_ADDRESS; +} + + +nub_bool_t +DNBProcessSharedLibrariesUpdated(nub_process_t pid) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + { + procSP->SharedLibrariesUpdated (); + return true; + } + return false; +} + +//---------------------------------------------------------------------- +// Get the current shared library information for a process. Only return +// the shared libraries that have changed since the last shared library +// state changed event if only_changed is non-zero. +//---------------------------------------------------------------------- +nub_size_t +DNBProcessGetSharedLibraryInfo (nub_process_t pid, nub_bool_t only_changed, struct DNBExecutableImageInfo **image_infos) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + return procSP->CopyImageInfos (image_infos, only_changed); + + // If we have no process, then return NULL for the shared library info + // and zero for shared library count + *image_infos = NULL; + return 0; +} + +//---------------------------------------------------------------------- +// Get the register set information for a specific thread. +//---------------------------------------------------------------------- +const DNBRegisterSetInfo * +DNBGetRegisterSetInfo (nub_size_t *num_reg_sets) +{ + return DNBArch::GetRegisterSetInfo (num_reg_sets); +} + + +//---------------------------------------------------------------------- +// Read a register value by register set and register index. +//---------------------------------------------------------------------- +nub_bool_t +DNBThreadGetRegisterValueByID (nub_process_t pid, nub_thread_t tid, uint32_t set, uint32_t reg, DNBRegisterValue *value) +{ + MachProcessSP procSP; + ::bzero (value, sizeof(DNBRegisterValue)); + if (GetProcessSP (pid, procSP)) + { + if (tid != INVALID_NUB_THREAD) + return procSP->GetRegisterValue (tid, set, reg, value); + } + return false; +} + +nub_bool_t +DNBThreadSetRegisterValueByID (nub_process_t pid, nub_thread_t tid, uint32_t set, uint32_t reg, const DNBRegisterValue *value) +{ + if (tid != INVALID_NUB_THREAD) + { + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + return procSP->SetRegisterValue (tid, set, reg, value); + } + return false; +} + +nub_size_t +DNBThreadGetRegisterContext (nub_process_t pid, nub_thread_t tid, void *buf, size_t buf_len) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + { + if (tid != INVALID_NUB_THREAD) + return procSP->GetThreadList().GetRegisterContext (tid, buf, buf_len); + } + ::bzero (buf, buf_len); + return 0; + +} + +nub_size_t +DNBThreadSetRegisterContext (nub_process_t pid, nub_thread_t tid, const void *buf, size_t buf_len) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + { + if (tid != INVALID_NUB_THREAD) + return procSP->GetThreadList().SetRegisterContext (tid, buf, buf_len); + } + return 0; +} + +//---------------------------------------------------------------------- +// Read a register value by name. +//---------------------------------------------------------------------- +nub_bool_t +DNBThreadGetRegisterValueByName (nub_process_t pid, nub_thread_t tid, uint32_t reg_set, const char *reg_name, DNBRegisterValue *value) +{ + MachProcessSP procSP; + ::bzero (value, sizeof(DNBRegisterValue)); + if (GetProcessSP (pid, procSP)) + { + const struct DNBRegisterSetInfo *set_info; + nub_size_t num_reg_sets = 0; + set_info = DNBGetRegisterSetInfo (&num_reg_sets); + if (set_info) + { + uint32_t set = reg_set; + uint32_t reg; + if (set == REGISTER_SET_ALL) + { + for (set = 1; set < num_reg_sets; ++set) + { + for (reg = 0; reg < set_info[set].num_registers; ++reg) + { + if (strcasecmp(reg_name, set_info[set].registers[reg].name) == 0) + return procSP->GetRegisterValue (tid, set, reg, value); + } + } + } + else + { + for (reg = 0; reg < set_info[set].num_registers; ++reg) + { + if (strcasecmp(reg_name, set_info[set].registers[reg].name) == 0) + return procSP->GetRegisterValue (tid, set, reg, value); + } + } + } + } + return false; +} + + +//---------------------------------------------------------------------- +// Read a register set and register number from the register name. +//---------------------------------------------------------------------- +nub_bool_t +DNBGetRegisterInfoByName (const char *reg_name, DNBRegisterInfo* info) +{ + const struct DNBRegisterSetInfo *set_info; + nub_size_t num_reg_sets = 0; + set_info = DNBGetRegisterSetInfo (&num_reg_sets); + if (set_info) + { + uint32_t set, reg; + for (set = 1; set < num_reg_sets; ++set) + { + for (reg = 0; reg < set_info[set].num_registers; ++reg) + { + if (strcasecmp(reg_name, set_info[set].registers[reg].name) == 0) + { + *info = set_info[set].registers[reg]; + return true; + } + } + } + + for (set = 1; set < num_reg_sets; ++set) + { + uint32_t reg; + for (reg = 0; reg < set_info[set].num_registers; ++reg) + { + if (set_info[set].registers[reg].alt == NULL) + continue; + + if (strcasecmp(reg_name, set_info[set].registers[reg].alt) == 0) + { + *info = set_info[set].registers[reg]; + return true; + } + } + } + } + + ::bzero (info, sizeof(DNBRegisterInfo)); + return false; +} + + +//---------------------------------------------------------------------- +// Set the name to address callback function that this nub can use +// for any name to address lookups that are needed. +//---------------------------------------------------------------------- +nub_bool_t +DNBProcessSetNameToAddressCallback (nub_process_t pid, DNBCallbackNameToAddress callback, void *baton) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + { + procSP->SetNameToAddressCallback (callback, baton); + return true; + } + return false; +} + + +//---------------------------------------------------------------------- +// Set the name to address callback function that this nub can use +// for any name to address lookups that are needed. +//---------------------------------------------------------------------- +nub_bool_t +DNBProcessSetSharedLibraryInfoCallback (nub_process_t pid, DNBCallbackCopyExecutableImageInfos callback, void *baton) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + { + procSP->SetSharedLibraryInfoCallback (callback, baton); + return true; + } + return false; +} + +nub_addr_t +DNBProcessLookupAddress (nub_process_t pid, const char *name, const char *shlib) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + { + return procSP->LookupSymbol (name, shlib); + } + return INVALID_NUB_ADDRESS; +} + + +nub_size_t +DNBProcessGetAvailableSTDOUT (nub_process_t pid, char *buf, nub_size_t buf_size) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + return procSP->GetAvailableSTDOUT (buf, buf_size); + return 0; +} + +nub_size_t +DNBProcessGetAvailableSTDERR (nub_process_t pid, char *buf, nub_size_t buf_size) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + return procSP->GetAvailableSTDERR (buf, buf_size); + return 0; +} + +nub_size_t +DNBProcessGetStopCount (nub_process_t pid) +{ + MachProcessSP procSP; + if (GetProcessSP (pid, procSP)) + return procSP->StopCount(); + return 0; +} + +nub_bool_t +DNBResolveExecutablePath (const char *path, char *resolved_path, size_t resolved_path_size) +{ + if (path == NULL || path[0] == '\0') + return false; + + char max_path[PATH_MAX]; + std::string result; + CFString::GlobPath(path, result); + + if (result.empty()) + result = path; + + if (realpath(path, max_path)) + { + // Found the path relatively... + ::strncpy(resolved_path, max_path, resolved_path_size); + return strlen(resolved_path) + 1 < resolved_path_size; + } + else + { + // Not a relative path, check the PATH environment variable if the + const char *PATH = getenv("PATH"); + if (PATH) + { + const char *curr_path_start = PATH; + const char *curr_path_end; + while (curr_path_start && *curr_path_start) + { + curr_path_end = strchr(curr_path_start, ':'); + if (curr_path_end == NULL) + { + result.assign(curr_path_start); + curr_path_start = NULL; + } + else if (curr_path_end > curr_path_start) + { + size_t len = curr_path_end - curr_path_start; + result.assign(curr_path_start, len); + curr_path_start += len + 1; + } + else + break; + + result += '/'; + result += path; + struct stat s; + if (stat(result.c_str(), &s) == 0) + { + ::strncpy(resolved_path, result.c_str(), resolved_path_size); + return result.size() + 1 < resolved_path_size; + } + } + } + } + return false; +} + diff --git a/lldb/tools/debugserver/source/DNB.h b/lldb/tools/debugserver/source/DNB.h new file mode 100644 index 00000000000..55a039e2699 --- /dev/null +++ b/lldb/tools/debugserver/source/DNB.h @@ -0,0 +1,141 @@ +//===-- DNB.h ---------------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 3/23/07. +// +//===----------------------------------------------------------------------===// + +#ifndef __DNB_h__ +#define __DNB_h__ + +#include "DNBDefs.h" +#include <mach/thread_info.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define DNB_EXPORT __attribute__((visibility("default"))) + +typedef bool (*DNBShouldCancelCallback) (void *); + +//---------------------------------------------------------------------- +// Process control +//---------------------------------------------------------------------- +nub_process_t DNBProcessLaunch (const char *path, char const *argv[], const char *envp[], const char *stdio_path, nub_launch_flavor_t launch_flavor, char *err_str, size_t err_len) DNB_EXPORT; +nub_process_t DNBProcessAttach (nub_process_t pid, struct timespec *timeout, char *err_str, size_t err_len) DNB_EXPORT; +nub_process_t DNBProcessAttachByName (const char *name, struct timespec *timeout, char *err_str, size_t err_len) DNB_EXPORT; +nub_process_t DNBProcessAttachWait (const char *wait_name, nub_launch_flavor_t launch_flavor, struct timespec *timeout, useconds_t interval, char *err_str, size_t err_len, DNBShouldCancelCallback should_cancel = NULL, void *callback_data = NULL) DNB_EXPORT; +// Resume a process with exact instructions on what to do with each thread: +// - If no thread actions are supplied (actions is NULL or num_actions is zero), +// then all threads are continued. +// - If any thread actions are supplied, then each thread will do as it is told +// by the action. A default actions for any threads that don't have an +// explicit thread action can be made by making a thread action with a tid of +// INVALID_NUB_THREAD. If there is no default action, those threads will +// remain stopped. +nub_bool_t DNBProcessResume (nub_process_t pid, const DNBThreadResumeAction *actions, size_t num_actions) DNB_EXPORT; +nub_bool_t DNBProcessHalt (nub_process_t pid) DNB_EXPORT; +nub_bool_t DNBProcessDetach (nub_process_t pid) DNB_EXPORT; +nub_bool_t DNBProcessSignal (nub_process_t pid, int signal) DNB_EXPORT; +nub_bool_t DNBProcessKill (nub_process_t pid) DNB_EXPORT; +nub_size_t DNBProcessMemoryRead (nub_process_t pid, nub_addr_t addr, nub_size_t size, void *buf) DNB_EXPORT; +nub_size_t DNBProcessMemoryWrite (nub_process_t pid, nub_addr_t addr, nub_size_t size, const void *buf) DNB_EXPORT; +nub_addr_t DNBProcessMemoryAllocate (nub_process_t pid, nub_size_t size, uint32_t permissions) DNB_EXPORT; +nub_bool_t DNBProcessMemoryDeallocate (nub_process_t pid, nub_addr_t addr) DNB_EXPORT; +//---------------------------------------------------------------------- +// Process status +//---------------------------------------------------------------------- +nub_bool_t DNBProcessIsAlive (nub_process_t pid) DNB_EXPORT; +nub_state_t DNBProcessGetState (nub_process_t pid) DNB_EXPORT; +nub_bool_t DNBProcessGetExitStatus (nub_process_t pid, int *status) DNB_EXPORT; +nub_bool_t DNBProcessSetExitStatus (nub_process_t pid, int status) DNB_EXPORT; +nub_size_t DNBProcessGetNumThreads (nub_process_t pid) DNB_EXPORT; +nub_thread_t DNBProcessGetCurrentThread (nub_process_t pid) DNB_EXPORT; +nub_thread_t DNBProcessSetCurrentThread (nub_process_t pid, nub_thread_t tid) DNB_EXPORT; +nub_thread_t DNBProcessGetThreadAtIndex (nub_process_t pid, nub_size_t thread_idx) DNB_EXPORT; +nub_addr_t DNBProcessGetSharedLibraryInfoAddress (nub_process_t pid) DNB_EXPORT; +nub_bool_t DNBProcessSharedLibrariesUpdated (nub_process_t pid) DNB_EXPORT; +nub_size_t DNBProcessGetSharedLibraryInfo (nub_process_t pid, nub_bool_t only_changed, DNBExecutableImageInfo **image_infos) DNB_EXPORT; +nub_bool_t DNBProcessSetNameToAddressCallback (nub_process_t pid, DNBCallbackNameToAddress callback, void *baton) DNB_EXPORT; +nub_bool_t DNBProcessSetSharedLibraryInfoCallback (nub_process_t pid, DNBCallbackCopyExecutableImageInfos callback, void *baton) DNB_EXPORT; +nub_addr_t DNBProcessLookupAddress (nub_process_t pid, const char *name, const char *shlib) DNB_EXPORT; +nub_size_t DNBProcessGetAvailableSTDOUT (nub_process_t pid, char *buf, nub_size_t buf_size) DNB_EXPORT; +nub_size_t DNBProcessGetAvailableSTDERR (nub_process_t pid, char *buf, nub_size_t buf_size) DNB_EXPORT; +nub_size_t DNBProcessGetStopCount (nub_process_t pid) DNB_EXPORT; +//---------------------------------------------------------------------- +// Process executable and arguments +//---------------------------------------------------------------------- +const char * DNBProcessGetExecutablePath (nub_process_t pid) DNB_EXPORT; +const char * DNBProcessGetArgumentAtIndex (nub_process_t pid, nub_size_t idx) DNB_EXPORT; +nub_size_t DNBProcessGetArgumentCount (nub_process_t pid) DNB_EXPORT; + +//---------------------------------------------------------------------- +// Process events +//---------------------------------------------------------------------- +nub_event_t DNBProcessWaitForEvents (nub_process_t pid, nub_event_t event_mask, bool wait_for_set, struct timespec* timeout) DNB_EXPORT; +void DNBProcessResetEvents (nub_process_t pid, nub_event_t event_mask) DNB_EXPORT; +void DNBProcessInterruptEvents (nub_process_t pid) DNB_EXPORT; + +//---------------------------------------------------------------------- +// Thread functions +//---------------------------------------------------------------------- +const char * DNBThreadGetName (nub_process_t pid, nub_thread_t tid) DNB_EXPORT; +nub_bool_t DNBThreadGetIdentifierInfo (nub_process_t pid, nub_thread_t tid, thread_identifier_info_data_t *ident_info) DNB_EXPORT; +nub_state_t DNBThreadGetState (nub_process_t pid, nub_thread_t tid) DNB_EXPORT; +nub_bool_t DNBThreadGetRegisterValueByID (nub_process_t pid, nub_thread_t tid, uint32_t set, uint32_t reg, DNBRegisterValue *value) DNB_EXPORT; +nub_bool_t DNBThreadSetRegisterValueByID (nub_process_t pid, nub_thread_t tid, uint32_t set, uint32_t reg, const DNBRegisterValue *value) DNB_EXPORT; +nub_size_t DNBThreadGetRegisterContext (nub_process_t pid, nub_thread_t tid, void *buf, size_t buf_len) DNB_EXPORT; +nub_size_t DNBThreadSetRegisterContext (nub_process_t pid, nub_thread_t tid, const void *buf, size_t buf_len) DNB_EXPORT; +nub_bool_t DNBThreadGetRegisterValueByName (nub_process_t pid, nub_thread_t tid, uint32_t set, const char *name, DNBRegisterValue *value) DNB_EXPORT; +nub_bool_t DNBThreadGetStopReason (nub_process_t pid, nub_thread_t tid, DNBThreadStopInfo *stop_info) DNB_EXPORT; +const char * DNBThreadGetInfo (nub_process_t pid, nub_thread_t tid) DNB_EXPORT; +//---------------------------------------------------------------------- +// Breakpoint functions +//---------------------------------------------------------------------- +nub_break_t DNBBreakpointSet (nub_process_t pid, nub_addr_t addr, nub_size_t size, nub_bool_t hardware) DNB_EXPORT; +nub_bool_t DNBBreakpointClear (nub_process_t pid, nub_break_t breakID) DNB_EXPORT; +nub_ssize_t DNBBreakpointGetHitCount (nub_process_t pid, nub_break_t breakID) DNB_EXPORT; +nub_ssize_t DNBBreakpointGetIgnoreCount (nub_process_t pid, nub_break_t breakID) DNB_EXPORT; +nub_bool_t DNBBreakpointSetIgnoreCount (nub_process_t pid, nub_break_t breakID, nub_size_t ignore_count) DNB_EXPORT; +nub_bool_t DNBBreakpointSetCallback (nub_process_t pid, nub_break_t breakID, DNBCallbackBreakpointHit callback, void *baton) DNB_EXPORT; +void DNBBreakpointPrint (nub_process_t pid, nub_break_t breakID) DNB_EXPORT; + +//---------------------------------------------------------------------- +// Watchpoint functions +//---------------------------------------------------------------------- +nub_watch_t DNBWatchpointSet (nub_process_t pid, nub_addr_t addr, nub_size_t size, uint32_t watch_flags, nub_bool_t hardware) DNB_EXPORT; +nub_bool_t DNBWatchpointClear (nub_process_t pid, nub_watch_t watchID) DNB_EXPORT; +nub_ssize_t DNBWatchpointGetHitCount (nub_process_t pid, nub_watch_t watchID) DNB_EXPORT; +nub_ssize_t DNBWatchpointGetIgnoreCount (nub_process_t pid, nub_watch_t watchID) DNB_EXPORT; +nub_bool_t DNBWatchpointSetIgnoreCount (nub_process_t pid, nub_watch_t watchID, nub_size_t ignore_count) DNB_EXPORT; +nub_bool_t DNBWatchpointSetCallback (nub_process_t pid, nub_watch_t watchID, DNBCallbackBreakpointHit callback, void *baton) DNB_EXPORT; +void DNBWatchpointPrint (nub_process_t pid, nub_watch_t watchID) DNB_EXPORT; + +const DNBRegisterSetInfo * + DNBGetRegisterSetInfo (nub_size_t *num_reg_sets) DNB_EXPORT; +nub_bool_t DNBGetRegisterInfoByName (const char *reg_name, DNBRegisterInfo* info) DNB_EXPORT; + +//---------------------------------------------------------------------- +// Printf style formatting for printing values in the inferior memory +// space and registers. +//---------------------------------------------------------------------- +nub_size_t DNBPrintf (nub_process_t pid, nub_thread_t tid, nub_addr_t addr, FILE *file, const char *format) DNB_EXPORT; + +//---------------------------------------------------------------------- +// Other static nub information calls. +//---------------------------------------------------------------------- +const char * DNBStateAsString (nub_state_t state) DNB_EXPORT; +nub_bool_t DNBResolveExecutablePath (const char *path, char *resolved_path, size_t resolved_path_size) DNB_EXPORT; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lldb/tools/debugserver/source/DNBArch.h b/lldb/tools/debugserver/source/DNBArch.h new file mode 100644 index 00000000000..3eb2362aa0f --- /dev/null +++ b/lldb/tools/debugserver/source/DNBArch.h @@ -0,0 +1,61 @@ +//===-- DNBArch.h -----------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/24/07. +// +//===----------------------------------------------------------------------===// + +#ifndef __DebugNubArch_h__ +#define __DebugNubArch_h__ + +#include "DNBDefs.h" +#include "MacOSX/MachException.h" + +#include <mach/mach.h> +#include <stdio.h> + +struct DNBRegisterValue; +struct DNBRegisterSetInfo; + +class DNBArchProtocol +{ +public: + static const DNBRegisterSetInfo * + GetRegisterSetInfo (nub_size_t *num_reg_sets); + + virtual bool GetRegisterValue (int set, int reg, DNBRegisterValue *value) = 0; + virtual bool SetRegisterValue (int set, int reg, const DNBRegisterValue *value) = 0; + virtual nub_size_t GetRegisterContext (void *buf, nub_size_t buf_len) = 0; + virtual nub_size_t SetRegisterContext (const void *buf, nub_size_t buf_len) = 0; + + virtual kern_return_t GetRegisterState (int set, bool force) = 0; + virtual kern_return_t SetRegisterState (int set) = 0; + virtual bool RegisterSetStateIsValid (int set) const = 0; + + virtual uint64_t GetPC (uint64_t failValue) = 0; // Get program counter + virtual uint64_t GetSP (uint64_t failValue) = 0; // Get stack pointer + virtual void ThreadWillResume () = 0; + virtual bool ThreadDidStop () = 0; + virtual bool NotifyException (MachException::Data& exc) { return false; } + virtual uint32_t NumSupportedHardwareBreakpoints() { return 0; } + virtual uint32_t NumSupportedHardwareWatchpoints() { return 0; } + virtual uint32_t EnableHardwareBreakpoint (nub_addr_t addr, nub_size_t size) { return INVALID_NUB_HW_INDEX; } + virtual uint32_t EnableHardwareWatchpoint (nub_addr_t addr, nub_size_t size, bool read, bool write) { return INVALID_NUB_HW_INDEX; } + virtual bool DisableHardwareBreakpoint (uint32_t hw_index) { return false; } + virtual bool DisableHardwareWatchpoint (uint32_t hw_index) { return false; } + virtual bool StepNotComplete () { return false; } +}; + + +#include "MacOSX/arm/DNBArchImpl.h" +#include "MacOSX/i386/DNBArchImplI386.h" +#include "MacOSX/x86_64/DNBArchImplX86_64.h" +#include "MacOSX/ppc/DNBArchImpl.h" + +#endif diff --git a/lldb/tools/debugserver/source/DNBBreakpoint.cpp b/lldb/tools/debugserver/source/DNBBreakpoint.cpp new file mode 100644 index 00000000000..837f53260cf --- /dev/null +++ b/lldb/tools/debugserver/source/DNBBreakpoint.cpp @@ -0,0 +1,303 @@ +//===-- DNBBreakpoint.cpp ---------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/29/07. +// +//===----------------------------------------------------------------------===// + +#include "DNBBreakpoint.h" +#include <algorithm> +#include "DNBLog.h" + + +#pragma mark -- DNBBreakpoint +DNBBreakpoint::DNBBreakpoint(nub_addr_t addr, nub_size_t byte_size, nub_thread_t tid, bool hardware) : + m_breakID(GetNextID()), + m_tid(tid), + m_byte_size(byte_size), + m_opcode(), + m_addr(addr), + m_enabled(0), + m_hw_preferred(hardware), + m_is_watchpoint(0), + m_watch_read(0), + m_watch_write(0), + m_hw_index(INVALID_NUB_HW_INDEX), + m_hit_count(0), + m_ignore_count(0), + m_callback(NULL), + m_callback_baton(NULL) +{ +} + +DNBBreakpoint::~DNBBreakpoint() +{ +} + +nub_break_t +DNBBreakpoint::GetNextID() +{ + static uint32_t g_nextBreakID = 0; + return ++g_nextBreakID; +} + +void +DNBBreakpoint::SetCallback(DNBCallbackBreakpointHit callback, void *callback_baton) +{ + m_callback = callback; + m_callback_baton = callback_baton; +} + + +// RETURNS - true if we should stop at this breakpoint, false if we +// should continue. + +bool +DNBBreakpoint::BreakpointHit(nub_process_t pid, nub_thread_t tid) +{ + m_hit_count++; + + if (m_hit_count > m_ignore_count) + { + if (m_callback) + return m_callback(pid, tid, GetID(), m_callback_baton); + return true; + } + return false; +} + +void +DNBBreakpoint::Dump() const +{ + if (IsBreakpoint()) + { + DNBLog("DNBBreakpoint %u: tid = %4.4x addr = %8.8p state = %s type = %s breakpoint hw_index = %i hit_count = %-4u ignore_count = %-4u callback = %8.8p baton = %8.8p", + m_breakID, + m_tid, + m_addr, + m_enabled ? "enabled " : "disabled", + IsHardware() ? "hardware" : "software", + GetHardwareIndex(), + GetHitCount(), + GetIgnoreCount(), + m_callback, + m_callback_baton); + } + else + { + DNBLog("DNBBreakpoint %u: tid = %4.4x addr = %8.8p size = %u state = %s type = %s watchpoint (%s%s) hw_index = %i hit_count = %-4u ignore_count = %-4u callback = %8.8p baton = %8.8p", + m_breakID, + m_tid, + m_addr, + m_byte_size, + m_enabled ? "enabled " : "disabled", + IsHardware() ? "hardware" : "software", + m_watch_read ? "r" : "", + m_watch_write ? "w" : "", + GetHardwareIndex(), + GetHitCount(), + GetIgnoreCount(), + m_callback, + m_callback_baton); + } +} + +#pragma mark -- DNBBreakpointList + +DNBBreakpointList::DNBBreakpointList() +{ +} + +DNBBreakpointList::~DNBBreakpointList() +{ +} + + +nub_break_t +DNBBreakpointList::Add(const DNBBreakpoint& bp) +{ + m_breakpoints.push_back(bp); + return m_breakpoints.back().GetID(); +} + +bool +DNBBreakpointList::ShouldStop(nub_process_t pid, nub_thread_t tid, nub_break_t breakID) +{ + DNBBreakpoint *bp = FindByID (breakID); + if (bp) + { + // Let the breakpoint decide if it should stop here (could not have + // reached it's target hit count yet, or it could have a callback + // that decided it shouldn't stop (shared library loads/unloads). + return bp->BreakpointHit(pid, tid); + } + // We should stop here since this breakpoint isn't valid anymore or it + // doesn't exist. + return true; +} + +nub_break_t +DNBBreakpointList::FindIDByAddress (nub_addr_t addr) +{ + DNBBreakpoint *bp = FindByAddress (addr); + if (bp) + { + DNBLogThreadedIf(LOG_BREAKPOINTS, "DNBBreakpointList::%s ( addr = 0x%8.8llx ) => %u", __FUNCTION__, (uint64_t)addr, bp->GetID()); + return bp->GetID(); + } + DNBLogThreadedIf(LOG_BREAKPOINTS, "DNBBreakpointList::%s ( addr = 0x%8.8llx ) => NONE", __FUNCTION__, (uint64_t)addr); + return INVALID_NUB_BREAK_ID; +} + +bool +DNBBreakpointList::Remove (nub_break_t breakID) +{ + iterator pos = GetBreakIDIterator(breakID); // Predicate + if (pos != m_breakpoints.end()) + { + m_breakpoints.erase(pos); + return true; + } + return false; +} + + +class BreakpointIDMatches +{ +public: + BreakpointIDMatches (nub_break_t breakID) : m_breakID(breakID) {} + bool operator() (const DNBBreakpoint& bp) const + { + return m_breakID == bp.GetID(); + } + private: + const nub_break_t m_breakID; +}; + +class BreakpointAddressMatches +{ +public: + BreakpointAddressMatches (nub_addr_t addr) : m_addr(addr) {} + bool operator() (const DNBBreakpoint& bp) const + { + return m_addr == bp.Address(); + } + private: + const nub_addr_t m_addr; +}; + +DNBBreakpointList::iterator +DNBBreakpointList::GetBreakIDIterator (nub_break_t breakID) +{ + return std::find_if(m_breakpoints.begin(), m_breakpoints.end(), // Search full range + BreakpointIDMatches(breakID)); // Predicate +} + +DNBBreakpointList::const_iterator +DNBBreakpointList::GetBreakIDConstIterator (nub_break_t breakID) const +{ + return std::find_if(m_breakpoints.begin(), m_breakpoints.end(), // Search full range + BreakpointIDMatches(breakID)); // Predicate +} + +DNBBreakpoint * +DNBBreakpointList::FindByID (nub_break_t breakID) +{ + iterator pos = GetBreakIDIterator(breakID); + if (pos != m_breakpoints.end()) + return &(*pos); + + return NULL; +} + +const DNBBreakpoint * +DNBBreakpointList::FindByID (nub_break_t breakID) const +{ + const_iterator pos = GetBreakIDConstIterator(breakID); + if (pos != m_breakpoints.end()) + return &(*pos); + + return NULL; +} + +DNBBreakpoint * +DNBBreakpointList::FindByAddress (nub_addr_t addr) +{ + iterator end = m_breakpoints.end(); + iterator pos = std::find_if(m_breakpoints.begin(), end, // Search full range + BreakpointAddressMatches(addr)); // Predicate + if (pos != end) + return &(*pos); + + return NULL; +} + +const DNBBreakpoint * +DNBBreakpointList::FindByAddress (nub_addr_t addr) const +{ + const_iterator end = m_breakpoints.end(); + const_iterator pos = std::find_if(m_breakpoints.begin(), end, // Search full range + BreakpointAddressMatches(addr)); // Predicate + if (pos != end) + return &(*pos); + + return NULL; +} + +bool +DNBBreakpointList::SetCallback(nub_break_t breakID, DNBCallbackBreakpointHit callback, void *callback_baton) +{ + DNBBreakpoint *bp = FindByID (breakID); + if (bp) + { + bp->SetCallback(callback, callback_baton); + return true; + } + return false; +} + + +void +DNBBreakpointList::Dump() const +{ + const_iterator pos; + const_iterator end = m_breakpoints.end(); + for (pos = m_breakpoints.begin(); pos != end; ++pos) + (*pos).Dump(); +} + + +DNBBreakpoint * +DNBBreakpointList::GetByIndex (uint32_t i) +{ + iterator end = m_breakpoints.end(); + iterator pos; + uint32_t curr_i = 0; + for (pos = m_breakpoints.begin(), curr_i = 0; pos != end; ++pos, ++curr_i) + { + if (curr_i == i) + return &(*pos); + } + return NULL; +} + +const DNBBreakpoint * +DNBBreakpointList::GetByIndex (uint32_t i) const +{ + const_iterator end = m_breakpoints.end(); + const_iterator pos; + uint32_t curr_i = 0; + for (pos = m_breakpoints.begin(), curr_i = 0; pos != end; ++pos, ++curr_i) + { + if (curr_i == i) + return &(*pos); + } + return NULL; +} + diff --git a/lldb/tools/debugserver/source/DNBBreakpoint.h b/lldb/tools/debugserver/source/DNBBreakpoint.h new file mode 100644 index 00000000000..28c967a0525 --- /dev/null +++ b/lldb/tools/debugserver/source/DNBBreakpoint.h @@ -0,0 +1,159 @@ +//===-- DNBBreakpoint.h -----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/29/07. +// +//===----------------------------------------------------------------------===// + +#ifndef __DNBBreakpoint_h__ +#define __DNBBreakpoint_h__ + +#include <list> + +#include "DNBDefs.h" + +class DNBBreakpoint +{ +public: + DNBBreakpoint(nub_addr_t m_addr, nub_size_t byte_size, nub_thread_t tid, bool hardware); + ~DNBBreakpoint(); + + nub_break_t GetID() const { return m_breakID; } + nub_size_t ByteSize() const { return m_byte_size; } + uint8_t * SavedOpcodeBytes() { return &m_opcode[0]; } + const uint8_t * + SavedOpcodeBytes() const { return &m_opcode[0]; } + nub_addr_t Address() const { return m_addr; } + nub_thread_t ThreadID() const { return m_tid; } + bool IsEnabled() const { return m_enabled; } + bool IntersectsRange(nub_addr_t addr, nub_size_t size, nub_addr_t *intersect_addr, nub_size_t *intersect_size, nub_size_t *opcode_offset) const + { + // We only use software traps for software breakpoints + if (IsBreakpoint() && IsEnabled() && !IsHardware()) + { + if (m_byte_size > 0) + { + const nub_addr_t bp_end_addr = m_addr + m_byte_size; + const nub_addr_t end_addr = addr + size; + // Is the breakpoint end address before the passed in start address? + if (bp_end_addr <= addr) + return false; + // Is the breakpoint start address after passed in end address? + if (end_addr <= m_addr) + return false; + if (intersect_addr || intersect_size || opcode_offset) + { + if (m_addr < addr) + { + if (intersect_addr) + *intersect_addr = addr; + if (intersect_size) + *intersect_size = std::min<nub_addr_t>(bp_end_addr, end_addr) - addr; + if (opcode_offset) + *opcode_offset = addr - m_addr; + } + else + { + if (intersect_addr) + *intersect_addr = m_addr; + if (intersect_size) + *intersect_size = std::min<nub_addr_t>(bp_end_addr, end_addr) - m_addr; + if (opcode_offset) + *opcode_offset = 0; + } + } + return true; + } + } + return false; + } + void SetEnabled(uint32_t enabled) + { + if (!enabled) + SetHardwareIndex(INVALID_NUB_HW_INDEX); + m_enabled = enabled; + } + void SetIsWatchpoint (uint32_t type) + { + m_is_watchpoint = 1; + m_watch_read = (type & WATCH_TYPE_READ) != 0; + m_watch_write = (type & WATCH_TYPE_WRITE) != 0; + } + bool IsBreakpoint() const { return m_is_watchpoint == 0; } + bool IsWatchpoint() const { return m_is_watchpoint == 1; } + bool WatchpointRead() const { return m_watch_read != 0; } + bool WatchpointWrite() const { return m_watch_write != 0; } + bool HardwarePreferred() const { return m_hw_preferred; } + bool IsHardware() const { return m_hw_index != INVALID_NUB_HW_INDEX; } + uint32_t GetHardwareIndex() const { return m_hw_index; } + void SetHardwareIndex(uint32_t hw_index) { m_hw_index = hw_index; } +// StateType GetState() const { return m_state; } +// void SetState(StateType newState) { m_state = newState; } + int32_t GetHitCount() const { return m_hit_count; } + int32_t GetIgnoreCount() const { return m_ignore_count; } + void SetIgnoreCount(int32_t n) { m_ignore_count = n; } + bool BreakpointHit(nub_process_t pid, nub_thread_t tid); + void SetCallback(DNBCallbackBreakpointHit callback, void *callback_baton); + void Dump() const; + +private: + nub_break_t m_breakID; // The unique identifier for this breakpoint + nub_thread_t m_tid; // Thread ID for the breakpoint (can be INVALID_NUB_THREAD for all threads) + nub_size_t m_byte_size; // Length in bytes of the breakpoint if set in memory + uint8_t m_opcode[8]; // Saved opcode bytes + nub_addr_t m_addr; // Address of this breakpoint + uint32_t m_enabled:1, // Flags for this breakpoint + m_hw_preferred:1, // 1 if this point has been requested to be set using hardware (which may fail due to lack of resources) + m_is_watchpoint:1, // 1 if this is a watchpoint + m_watch_read:1, // 1 if we stop when the watched data is read from + m_watch_write:1; // 1 if we stop when the watched data is written to + uint32_t m_hw_index; // The hardware resource index for this breakpoint/watchpoint + int32_t m_hit_count; // Number of times this breakpoint has been hit + int32_t m_ignore_count; // Number of times to ignore this breakpoint + DNBCallbackBreakpointHit + m_callback; // Callback to call when this breakpoint gets hit + void * m_callback_baton; // Callback user data to pass to callback + + static nub_break_t GetNextID(); + +}; + + +class DNBBreakpointList +{ +public: + DNBBreakpointList(); + ~DNBBreakpointList(); + + nub_break_t Add (const DNBBreakpoint& bp); + nub_break_t FindIDByAddress (nub_addr_t addr); + bool ShouldStop (nub_process_t pid, nub_thread_t tid, nub_break_t breakID); + bool Remove (nub_break_t breakID); + bool SetCallback (nub_break_t breakID, DNBCallbackBreakpointHit callback, void *callback_baton); + DNBBreakpoint * FindByAddress (nub_addr_t addr); + const DNBBreakpoint * FindByAddress (nub_addr_t addr) const; + DNBBreakpoint * FindByID (nub_break_t breakID); + const DNBBreakpoint * FindByID (nub_break_t breakID) const; + void Dump () const; + + size_t Size() const { return m_breakpoints.size(); } + DNBBreakpoint * GetByIndex (uint32_t i); + const DNBBreakpoint * GetByIndex (uint32_t i) const; + +protected: + typedef std::list<DNBBreakpoint> collection; + typedef collection::iterator iterator; + typedef collection::const_iterator const_iterator; + iterator GetBreakIDIterator(nub_break_t breakID); + const_iterator GetBreakIDConstIterator(nub_break_t breakID) const; + collection m_breakpoints; +}; + +#endif + diff --git a/lldb/tools/debugserver/source/DNBDataRef.cpp b/lldb/tools/debugserver/source/DNBDataRef.cpp new file mode 100644 index 00000000000..271c205e024 --- /dev/null +++ b/lldb/tools/debugserver/source/DNBDataRef.cpp @@ -0,0 +1,485 @@ +//===-- DNBDataRef.cpp ------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 1/11/06. +// +//===----------------------------------------------------------------------===// + +#include "DNBDataRef.h" +#include "DNBLog.h" +#include <assert.h> +#include <ctype.h> +#include <libkern/OSByteOrder.h> + +//---------------------------------------------------------------------- +// Constructor +//---------------------------------------------------------------------- + +DNBDataRef::DNBDataRef() : + m_start(NULL), + m_end(NULL), + m_swap(false), + m_ptrSize(0), + m_addrPCRelative(INVALID_NUB_ADDRESS), + m_addrTEXT(INVALID_NUB_ADDRESS), + m_addrDATA(INVALID_NUB_ADDRESS) +{ +} + + +//---------------------------------------------------------------------- +// Constructor +//---------------------------------------------------------------------- + +DNBDataRef::DNBDataRef(const uint8_t *start, size_t size, bool swap) : + m_start(start), + m_end(start+size), + m_swap(swap), + m_ptrSize(0), + m_addrPCRelative(INVALID_NUB_ADDRESS), + m_addrTEXT(INVALID_NUB_ADDRESS), + m_addrDATA(INVALID_NUB_ADDRESS) +{ +} + + +//---------------------------------------------------------------------- +// Destructor +//---------------------------------------------------------------------- + +DNBDataRef::~DNBDataRef() +{ +} + + +//---------------------------------------------------------------------- +// Get8 +//---------------------------------------------------------------------- +uint8_t +DNBDataRef::Get8(offset_t *offset_ptr) const +{ + uint8_t val = 0; + if ( ValidOffsetForDataOfSize(*offset_ptr, sizeof(val)) ) + { + val = *(m_start + *offset_ptr); + *offset_ptr += sizeof(val); + } + return val; +} + + +//---------------------------------------------------------------------- +// Get16 +//---------------------------------------------------------------------- +uint16_t +DNBDataRef::Get16(offset_t *offset_ptr) const +{ + uint16_t val = 0; + if ( ValidOffsetForDataOfSize(*offset_ptr, sizeof(val)) ) + { + const uint8_t *p = m_start + *offset_ptr; + val = *(uint16_t*)p; + + if (m_swap) + val = OSSwapInt16(val); + + // Advance the offset + *offset_ptr += sizeof(val); + } + return val; +} + + +//---------------------------------------------------------------------- +// Get32 +//---------------------------------------------------------------------- +uint32_t +DNBDataRef::Get32(offset_t *offset_ptr) const +{ + uint32_t val = 0; + if ( ValidOffsetForDataOfSize(*offset_ptr, sizeof(val)) ) + { + const uint8_t *p = m_start + *offset_ptr; + val = *(uint32_t*)p; + if (m_swap) + val = OSSwapInt32(val); + + // Advance the offset + *offset_ptr += sizeof(val); + } + return val; +} + + +//---------------------------------------------------------------------- +// Get64 +//---------------------------------------------------------------------- +uint64_t +DNBDataRef::Get64(offset_t *offset_ptr) const +{ + uint64_t val = 0; + if ( ValidOffsetForDataOfSize(*offset_ptr, sizeof(val)) ) + { + const uint8_t *p = m_start + *offset_ptr; + val = *(uint64_t*)p; + if (m_swap) + val = OSSwapInt64(val); + + // Advance the offset + *offset_ptr += sizeof(val); + } + return val; +} + + +//---------------------------------------------------------------------- +// GetMax32 +// +// Used for calls when the size can vary. Fill in extra cases if they +// are ever needed. +//---------------------------------------------------------------------- +uint32_t +DNBDataRef::GetMax32(offset_t *offset_ptr, uint32_t byte_size) const +{ + switch (byte_size) + { + case 1: return Get8 (offset_ptr); break; + case 2: return Get16(offset_ptr); break; + case 4: return Get32(offset_ptr); break; + default: + assert(!"GetMax32 unhandled case!"); + break; + } + return 0; +} + + +//---------------------------------------------------------------------- +// GetMax64 +// +// Used for calls when the size can vary. Fill in extra cases if they +// are ever needed. +//---------------------------------------------------------------------- +uint64_t +DNBDataRef::GetMax64(offset_t *offset_ptr, uint32_t size) const +{ + switch (size) + { + case 1: return Get8 (offset_ptr); break; + case 2: return Get16(offset_ptr); break; + case 4: return Get32(offset_ptr); break; + case 8: return Get64(offset_ptr); break; + default: + assert(!"GetMax64 unhandled case!"); + break; + } + return 0; +} + +//---------------------------------------------------------------------- +// GetPointer +// +// Extract a pointer value from the buffer. The pointer size must be +// set prior to using this using one of the SetPointerSize functions. +//---------------------------------------------------------------------- +uint64_t +DNBDataRef::GetPointer(offset_t *offset_ptr) const +{ + // Must set pointer size prior to using this call + assert(m_ptrSize != 0); + return GetMax64(offset_ptr, m_ptrSize); +} + +//---------------------------------------------------------------------- +// GetDwarfEHPtr +// +// Used for calls when the value type is specified by a DWARF EH Frame +// pointer encoding. +//---------------------------------------------------------------------- +/* +uint64_t +DNBDataRef::GetDwarfEHPtr(offset_t *offset_ptr, uint32_t encoding) const +{ + if (encoding == DW_EH_PE_omit) + return ULONG_LONG_MAX; // Value isn't in the buffer... + + uint64_t baseAddress = 0; + uint64_t addressValue = 0; + + BOOL signExtendValue = NO; + // Decode the base part or adjust our offset + switch (encoding & 0x70) + { + case DW_EH_PE_pcrel: + // SetEHPtrBaseAddresses should be called prior to extracting these + // so the base addresses are cached. + assert(m_addrPCRelative != INVALID_NUB_ADDRESS); + signExtendValue = YES; + baseAddress = *offset_ptr + m_addrPCRelative; + break; + + case DW_EH_PE_textrel: + // SetEHPtrBaseAddresses should be called prior to extracting these + // so the base addresses are cached. + assert(m_addrTEXT != INVALID_NUB_ADDRESS); + signExtendValue = YES; + baseAddress = m_addrTEXT; + break; + + case DW_EH_PE_datarel: + // SetEHPtrBaseAddresses should be called prior to extracting these + // so the base addresses are cached. + assert(m_addrDATA != INVALID_NUB_ADDRESS); + signExtendValue = YES; + baseAddress = m_addrDATA; + break; + + case DW_EH_PE_funcrel: + signExtendValue = YES; + break; + + case DW_EH_PE_aligned: + // SetPointerSize should be called prior to extracting these so the + // pointer size is cached + assert(m_ptrSize != 0); + if (m_ptrSize) + { + // Align to a address size boundary first + uint32_t alignOffset = *offset_ptr % m_ptrSize; + if (alignOffset) + offset_ptr += m_ptrSize - alignOffset; + } + break; + + default: + break; + } + + // Decode the value part + switch (encoding & DW_EH_PE_MASK_ENCODING) + { + case DW_EH_PE_absptr : addressValue = GetPointer(offset_ptr); break; + case DW_EH_PE_uleb128 : addressValue = Get_ULEB128(offset_ptr); break; + case DW_EH_PE_udata2 : addressValue = Get16(offset_ptr); break; + case DW_EH_PE_udata4 : addressValue = Get32(offset_ptr); break; + case DW_EH_PE_udata8 : addressValue = Get64(offset_ptr); break; + case DW_EH_PE_sleb128 : addressValue = Get_SLEB128(offset_ptr); break; + case DW_EH_PE_sdata2 : addressValue = (int16_t)Get16(offset_ptr); break; + case DW_EH_PE_sdata4 : addressValue = (int32_t)Get32(offset_ptr); break; + case DW_EH_PE_sdata8 : addressValue = (int64_t)Get64(offset_ptr); break; + default: + // Unhandled encoding type + assert(encoding); + break; + } + + // Since we promote everything to 64 bit, we may need to sign extend + if (signExtendValue && m_ptrSize < sizeof(baseAddress)) + { + uint64_t sign_bit = 1ull << ((m_ptrSize * 8ull) - 1ull); + if (sign_bit & addressValue) + { + uint64_t mask = ~sign_bit + 1; + addressValue |= mask; + } + } + return baseAddress + addressValue; +} +*/ + + +//---------------------------------------------------------------------- +// GetCStr +//---------------------------------------------------------------------- +const char * +DNBDataRef::GetCStr(offset_t *offset_ptr, uint32_t fixed_length) const +{ + const char *s = NULL; + if ( m_start < m_end ) + { + s = (char*)m_start + *offset_ptr; + + // Advance the offset + if (fixed_length) + *offset_ptr += fixed_length; + else + *offset_ptr += strlen(s) + 1; + } + return s; +} + + +//---------------------------------------------------------------------- +// GetData +//---------------------------------------------------------------------- +const uint8_t * +DNBDataRef::GetData(offset_t *offset_ptr, uint32_t length) const +{ + const uint8_t *data = NULL; + if ( length > 0 && ValidOffsetForDataOfSize(*offset_ptr, length) ) + { + data = m_start + *offset_ptr; + *offset_ptr += length; + } + return data; +} + + +//---------------------------------------------------------------------- +// Get_ULEB128 +//---------------------------------------------------------------------- +uint64_t +DNBDataRef::Get_ULEB128 (offset_t *offset_ptr) const +{ + uint64_t result = 0; + if ( m_start < m_end ) + { + int shift = 0; + const uint8_t *src = m_start + *offset_ptr; + uint8_t byte; + int bytecount = 0; + + while (src < m_end) + { + bytecount++; + byte = *src++; + result |= (byte & 0x7f) << shift; + shift += 7; + if ((byte & 0x80) == 0) + break; + } + + *offset_ptr += bytecount; + } + return result; +} + + +//---------------------------------------------------------------------- +// Get_SLEB128 +//---------------------------------------------------------------------- +int64_t +DNBDataRef::Get_SLEB128 (offset_t *offset_ptr) const +{ + int64_t result = 0; + + if ( m_start < m_end ) + { + int shift = 0; + int size = sizeof (uint32_t) * 8; + const uint8_t *src = m_start + *offset_ptr; + + uint8_t byte; + int bytecount = 0; + + while (src < m_end) + { + bytecount++; + byte = *src++; + result |= (byte & 0x7f) << shift; + shift += 7; + if ((byte & 0x80) == 0) + break; + } + + // Sign bit of byte is 2nd high order bit (0x40) + if (shift < size && (byte & 0x40)) + result |= - (1ll << shift); + + *offset_ptr += bytecount; + } + return result; +} + + +//---------------------------------------------------------------------- +// Skip_LEB128 +// +// Skips past ULEB128 and SLEB128 numbers (just updates the offset) +//---------------------------------------------------------------------- +void +DNBDataRef::Skip_LEB128 (offset_t *offset_ptr) const +{ + if ( m_start < m_end ) + { + const uint8_t *start = m_start + *offset_ptr; + const uint8_t *src = start; + + while ((src < m_end) && (*src++ & 0x80)) + /* Do nothing */; + + *offset_ptr += src - start; + } +} + +uint32_t +DNBDataRef::Dump +( + uint32_t startOffset, + uint32_t endOffset, + uint64_t offsetBase, + DNBDataRef::Type type, + uint32_t numPerLine, + const char *format +) +{ + uint32_t offset; + uint32_t count; + char str[1024]; + str[0] = '\0'; + int str_offset = 0; + + for (offset = startOffset, count = 0; ValidOffset(offset) && offset < endOffset; ++count) + { + if ((count % numPerLine) == 0) + { + // Print out any previous string + if (str[0] != '\0') + DNBLog("%s", str); + // Reset string offset and fill the current line string with address: + str_offset = 0; + str_offset += snprintf(str, sizeof(str), "0x%8.8llx:", (uint64_t)(offsetBase + (offset - startOffset))); + } + + // Make sure we don't pass the bounds of our current string buffer on each iteration through this loop + if (str_offset >= sizeof(str)) + { + // The last snprintf consumed our string buffer, we will need to dump this out + // and reset the string with no address + DNBLog("%s", str); + str_offset = 0; + str[0] = '\0'; + } + + // We already checked that there is at least some room in the string str above, so it is safe to make + // the snprintf call each time through this loop + switch (type) + { + default: + case TypeUInt8: str_offset += snprintf(str + str_offset, sizeof(str) - str_offset, format ? format : " %2.2x", Get8(&offset)); break; + case TypeChar: + { + char ch = Get8(&offset); + str_offset += snprintf(str + str_offset, sizeof(str) - str_offset, format ? format : " %c", isprint(ch) ? ch : ' '); + } + break; + case TypeUInt16: str_offset += snprintf(str + str_offset, sizeof(str) - str_offset, format ? format : " %4.4x", Get16(&offset)); break; + case TypeUInt32: str_offset += snprintf(str + str_offset, sizeof(str) - str_offset, format ? format : " %8.8x", Get32(&offset)); break; + case TypeUInt64: str_offset += snprintf(str + str_offset, sizeof(str) - str_offset, format ? format : " %16.16llx", Get64(&offset)); break; + case TypePointer: str_offset += snprintf(str + str_offset, sizeof(str) - str_offset, format ? format : " 0x%llx", GetPointer(&offset)); break; + case TypeULEB128: str_offset += snprintf(str + str_offset, sizeof(str) - str_offset, format ? format : " 0x%llx", Get_ULEB128(&offset)); break; + case TypeSLEB128: str_offset += snprintf(str + str_offset, sizeof(str) - str_offset, format ? format : " %lld", Get_SLEB128(&offset)); break; + } + } + + if (str[0] != '\0') + DNBLog("%s", str); + + return offset; // Return the offset at which we ended up +} diff --git a/lldb/tools/debugserver/source/DNBDataRef.h b/lldb/tools/debugserver/source/DNBDataRef.h new file mode 100644 index 00000000000..fbecb7d4a76 --- /dev/null +++ b/lldb/tools/debugserver/source/DNBDataRef.h @@ -0,0 +1,111 @@ +//===-- DNBDataRef.h --------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 1/11/06. +// +//===----------------------------------------------------------------------===// +// +// DNBDataRef is a class that can extract data in normal or byte +// swapped order from a data buffer that someone else owns. The data +// buffer needs to remain intact as long as the DNBDataRef object +// needs the data. Strings returned are pointers into the data buffer +// and will need to be copied if they are needed after the data buffer +// is no longer around. +// +//===----------------------------------------------------------------------===// + +#ifndef __DNBDataRef_h__ +#define __DNBDataRef_h__ + +#include "DNBDefs.h" +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <limits.h> + +class DNBDataRef +{ +public: + // For use with Dump + typedef enum + { + TypeUInt8 = 0, + TypeChar, + TypeUInt16, + TypeUInt32, + TypeUInt64, + TypePointer, + TypeULEB128, + TypeSLEB128 + } Type; + typedef uint32_t offset_t; + typedef nub_addr_t addr_t; + + DNBDataRef(); + DNBDataRef(const uint8_t *start, size_t size, bool swap); + ~DNBDataRef(); + void Clear() + { + DNBDataRef::SetData(NULL, 0); + m_swap = false; + } + + bool ValidOffset(offset_t offset) const { return (m_start < m_end) && ((uint32_t)(m_end - m_start) > offset); } + bool ValidOffsetForDataOfSize(offset_t offset, uint32_t num_bytes) const { return (m_start < m_end) && ((uint32_t)(m_end - m_start) > (offset + ((num_bytes > 0) ? (num_bytes - 1) : 0))); } + size_t GetSize() const { return m_end - m_start; } + const uint8_t * GetDataStart() const { return m_start; } + const uint8_t * GetDataEnd() const { return m_end; } + bool GetSwap() const { return m_swap; } + void SetSwap(bool swap) { m_swap = swap; } + void SetData(const uint8_t *start, size_t size) + { + m_start = start; + if (m_start != NULL) + m_end = start + size; + else + m_end = NULL; + } + uint8_t GetPointerSize() const { return m_ptrSize; } + void SetPointerSize(uint8_t size) { m_ptrSize = size; } + void SetEHPtrBaseAddrPCRelative(addr_t addr = INVALID_NUB_ADDRESS) { m_addrPCRelative = addr; } + void SetEHPtrBaseAddrTEXT(addr_t addr = INVALID_NUB_ADDRESS) { m_addrTEXT = addr; } + void SetEHPtrBaseAddrDATA(addr_t addr = INVALID_NUB_ADDRESS) { m_addrDATA = addr; } + uint8_t Get8(offset_t *offset_ptr) const; + uint16_t Get16(offset_t *offset_ptr) const; + uint32_t Get32(offset_t *offset_ptr) const; + uint64_t Get64(offset_t *offset_ptr) const; + uint32_t GetMax32(offset_t *offset_ptr, uint32_t byte_size) const; + uint64_t GetMax64(offset_t *offset_ptr, uint32_t byte_size) const; + uint64_t GetPointer(offset_t *offset_ptr) const; +// uint64_t GetDwarfEHPtr(offset_t *offset_ptr, uint32_t eh_ptr_enc) const; + const char * GetCStr(offset_t *offset_ptr, uint32_t fixed_length = 0) const; + const char * PeekCStr(offset_t offset) const + { + if (ValidOffset(offset)) + return (const char*)m_start + offset; + return NULL; + } + + const uint8_t * GetData( offset_t *offset_ptr, uint32_t length) const; + uint64_t Get_ULEB128 (offset_t *offset_ptr) const; + int64_t Get_SLEB128 (offset_t *offset_ptr) const; + void Skip_LEB128 (offset_t *offset_ptr) const; + + uint32_t Dump(offset_t startOffset, offset_t endOffset, uint64_t offsetBase, DNBDataRef::Type type, uint32_t numPerLine, const char *typeFormat = NULL); +protected: + const uint8_t * m_start; + const uint8_t * m_end; + bool m_swap; + uint8_t m_ptrSize; + addr_t m_addrPCRelative; + addr_t m_addrTEXT; + addr_t m_addrDATA; +}; + +#endif // #ifndef __DNBDataRef_h__ diff --git a/lldb/tools/debugserver/source/DNBDefs.h b/lldb/tools/debugserver/source/DNBDefs.h new file mode 100644 index 00000000000..23960ef4957 --- /dev/null +++ b/lldb/tools/debugserver/source/DNBDefs.h @@ -0,0 +1,331 @@ +//===-- DNBDefs.h -----------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/26/07. +// +//===----------------------------------------------------------------------===// + +#ifndef __DNBDefs_h__ +#define __DNBDefs_h__ + +#include <stdint.h> +#include <signal.h> +#include <stdio.h> +#include <sys/syslimits.h> +#include <unistd.h> + +//---------------------------------------------------------------------- +// Define nub_addr_t and the invalid address value from the architecture +//---------------------------------------------------------------------- +#if defined (__x86_64__) || defined (__ppc64__) + +//---------------------------------------------------------------------- +// 64 bit address architectures +//---------------------------------------------------------------------- +typedef uint64_t nub_addr_t; +#define INVALID_NUB_ADDRESS ((nub_addr_t)~0ull) + +#elif defined (__i386__) || defined (__powerpc__) || defined (__ppc__) || defined (__arm__) + +//---------------------------------------------------------------------- +// 32 bit address architectures +//---------------------------------------------------------------------- + +typedef uint32_t nub_addr_t; +#define INVALID_NUB_ADDRESS ((nub_addr_t)~0ul) + +#else + +//---------------------------------------------------------------------- +// Default to 64 bit address for unrecognized architectures. +//---------------------------------------------------------------------- + +#warning undefined architecture, defaulting to 8 byte addresses +typedef uint64_t nub_addr_t; +#define INVALID_NUB_ADDRESS ((nub_addr_t)~0ull) + + +#endif + +typedef size_t nub_size_t; +typedef ssize_t nub_ssize_t; +typedef uint32_t nub_break_t; +typedef uint32_t nub_watch_t; +typedef uint32_t nub_index_t; +typedef pid_t nub_process_t; +typedef unsigned int nub_thread_t; +typedef uint32_t nub_event_t; +typedef uint32_t nub_bool_t; + +#define INVALID_NUB_BREAK_ID ((nub_break_t)0) +#define INVALID_NUB_PROCESS ((nub_process_t)0) +#define INVALID_NUB_THREAD ((nub_thread_t)0) +#define INVALID_NUB_HW_INDEX UINT32_MAX +#define INVALID_NUB_REGNUM UINT32_MAX +#define NUB_GENERIC_ERROR UINT32_MAX + +#define NUB_BREAK_ID_IS_VALID(breakID) ((breakID) != (INVALID_NUB_BREAK_ID)) + +// Watchpoint types +#define WATCH_TYPE_READ (1u << 0) +#define WATCH_TYPE_WRITE (1u << 1) + +typedef enum +{ + eStateInvalid = 0, + eStateUnloaded, + eStateAttaching, + eStateLaunching, + eStateStopped, + eStateRunning, + eStateStepping, + eStateCrashed, + eStateDetached, + eStateExited, + eStateSuspended +} nub_state_t; + +typedef enum +{ + eLaunchFlavorDefault = 0, + eLaunchFlavorPosixSpawn, + eLaunchFlavorForkExec, +#if defined (__arm__) + eLaunchFlavorSpringBoard, +#endif +} nub_launch_flavor_t; + +#define NUB_STATE_IS_RUNNING(s) ((s) == eStateAttaching ||\ + (s) == eStateLaunching ||\ + (s) == eStateRunning ||\ + (s) == eStateStepping ||\ + (s) == eStateDetached) + +#define NUB_STATE_IS_STOPPED(s) ((s) == eStateUnloaded ||\ + (s) == eStateStopped ||\ + (s) == eStateCrashed ||\ + (s) == eStateExited) + +enum +{ + eEventProcessRunningStateChanged = 1 << 0, // The process has changed state to running + eEventProcessStoppedStateChanged = 1 << 1, // The process has changed state to stopped + eEventSharedLibsStateChange = 1 << 2, // Shared libraries loaded/unloaded state has changed + eEventStdioAvailable = 1 << 3, // Something is available on stdout/stderr + eEventProcessAsyncInterrupt = 1 << 4, // Gives the ability for any infinite wait calls to be interrupted + kAllEventsMask = eEventProcessRunningStateChanged | + eEventProcessStoppedStateChanged | + eEventSharedLibsStateChange | + eEventStdioAvailable | + eEventProcessAsyncInterrupt +}; + +#define LOG_VERBOSE (1u << 0) +#define LOG_PROCESS (1u << 1) +#define LOG_THREAD (1u << 2) +#define LOG_EXCEPTIONS (1u << 3) +#define LOG_SHLIB (1u << 4) +#define LOG_MEMORY (1u << 5) // Log memory reads/writes calls +#define LOG_MEMORY_DATA_SHORT (1u << 6) // Log short memory reads/writes bytes +#define LOG_MEMORY_DATA_LONG (1u << 7) // Log all memory reads/writes bytes +#define LOG_MEMORY_PROTECTIONS (1u << 8) // Log memory protection changes +#define LOG_BREAKPOINTS (1u << 9) +#define LOG_EVENTS (1u << 10) +#define LOG_WATCHPOINTS (1u << 11) +#define LOG_STEP (1u << 12) +#define LOG_TASK (1u << 13) +#define LOG_LO_USER (1u << 16) +#define LOG_HI_USER (1u << 31) +#define LOG_ALL 0xFFFFFFFFu +#define LOG_DEFAULT ((LOG_PROCESS) |\ + (LOG_TASK) |\ + (LOG_THREAD) |\ + (LOG_EXCEPTIONS) |\ + (LOG_SHLIB) |\ + (LOG_MEMORY) |\ + (LOG_BREAKPOINTS) |\ + (LOG_WATCHPOINTS) |\ + (LOG_STEP)) + + +#define REGISTER_SET_ALL 0 +// Generic Register set to be defined by each architecture for access to common +// register values. +#define REGISTER_SET_GENERIC ((uint32_t)0xFFFFFFFFu) +#define GENERIC_REGNUM_PC 0 // Program Counter +#define GENERIC_REGNUM_SP 1 // Stack Pointer +#define GENERIC_REGNUM_FP 2 // Frame Pointer +#define GENERIC_REGNUM_RA 3 // Return Address +#define GENERIC_REGNUM_FLAGS 4 // Processor flags register + +enum DNBRegisterType +{ + InvalidRegType = 0, + Uint, // unsigned integer + Sint, // signed integer + IEEE754, // float + Vector // vector registers +}; + +enum DNBRegisterFormat +{ + InvalidRegFormat = 0, + Binary, + Decimal, + Hex, + Float, + VectorOfSInt8, + VectorOfUInt8, + VectorOfSInt16, + VectorOfUInt16, + VectorOfSInt32, + VectorOfUInt32, + VectorOfFloat32, + VectorOfUInt128 +}; + +struct DNBRegisterInfo +{ + uint32_t set; // Register set + uint32_t reg; // Register number + const char *name; // Name of this register + const char *alt; // Alternate name + uint16_t type; // Type of the register bits (DNBRegisterType) + uint16_t format; // Default format for display (DNBRegisterFormat), + uint32_t size; // Size in bytes of the register + uint32_t offset; // Offset from the beginning of the register context + uint32_t reg_gcc; // GCC register number (INVALID_NUB_REGNUM when none) + uint32_t reg_dwarf; // DWARF register number (INVALID_NUB_REGNUM when none) + uint32_t reg_generic; // Generic register number (INVALID_NUB_REGNUM when none) + uint32_t reg_gdb; // The GDB register number (INVALID_NUB_REGNUM when none) +}; + +struct DNBRegisterSetInfo +{ + const char *name; // Name of this register set + const struct DNBRegisterInfo *registers; // An array of register descriptions + nub_size_t num_registers; // The number of registers in REGISTERS array above +}; + +struct DNBThreadResumeAction +{ + nub_thread_t tid; // The thread ID that this action applies to, INVALID_NUB_THREAD for the default thread action + nub_state_t state; // Valid values are eStateStopped/eStateSuspended, eStateRunning, and eStateStepping. + int signal; // When resuming this thread, resume it with this signal + nub_addr_t addr; // If not INVALID_NUB_ADDRESS, then set the PC for the thread to ADDR before resuming/stepping +}; + +enum DNBThreadStopType +{ + eStopTypeInvalid = 0, + eStopTypeSignal, + eStopTypeException +}; + +enum DNBMemoryPermissions +{ + eMemoryPermissionsWritable = (1 << 0), + eMemoryPermissionsReadable = (1 << 1), + eMemoryPermissionsExecutable = (1 << 2) +}; + +#define DNB_THREAD_STOP_INFO_MAX_DESC_LENGTH 256 +#define DNB_THREAD_STOP_INFO_MAX_EXC_DATA 8 + +//---------------------------------------------------------------------- +// DNBThreadStopInfo +// +// Describes the reason a thread stopped. +//---------------------------------------------------------------------- +struct DNBThreadStopInfo +{ + DNBThreadStopType reason; + char description[DNB_THREAD_STOP_INFO_MAX_DESC_LENGTH]; + union + { + // eStopTypeSignal + struct + { + uint32_t signo; + } signal; + + // eStopTypeException + struct + { + uint32_t type; + nub_size_t data_count; + nub_addr_t data[DNB_THREAD_STOP_INFO_MAX_EXC_DATA]; + } exception; + } details; +}; + + +struct DNBRegisterValue +{ + struct DNBRegisterInfo info; // Register information for this register + union + { + int8_t sint8; + int16_t sint16; + int32_t sint32; + int64_t sint64; + uint8_t uint8; + uint16_t uint16; + uint32_t uint32; + uint64_t uint64; + float float32; + double float64; + int8_t v_sint8[16]; + int16_t v_sint16[8]; + int32_t v_sint32[4]; + int64_t v_sint64[2]; + uint8_t v_uint8[16]; + uint16_t v_uint16[8]; + uint32_t v_uint32[4]; + uint64_t v_uint64[2]; + float v_float32[4]; + double v_float64[2]; + void *pointer; + char *c_str; + } value; +}; + +enum DNBSharedLibraryState +{ + eShlibStateUnloaded = 0, + eShlibStateLoaded = 1 +}; + +#ifndef DNB_MAX_SEGMENT_NAME_LENGTH +#define DNB_MAX_SEGMENT_NAME_LENGTH 32 +#endif + +struct DNBSegment +{ + char name[DNB_MAX_SEGMENT_NAME_LENGTH]; + nub_addr_t addr; + nub_addr_t size; +}; + +struct DNBExecutableImageInfo +{ + char name[PATH_MAX]; // Name of the executable image (usually a full path) + uint32_t state; // State of the executable image (see enum DNBSharedLibraryState) + nub_addr_t header_addr; // Executable header address + uuid_t uuid; // Unique indentifier for matching with symbols + uint32_t num_segments; // Number of contiguous memory segments to in SEGMENTS array + DNBSegment *segments; // Array of contiguous memory segments in executable +}; + +typedef nub_bool_t (*DNBCallbackBreakpointHit)(nub_process_t pid, nub_thread_t tid, nub_break_t breakID, void *baton); +typedef nub_addr_t (*DNBCallbackNameToAddress)(nub_process_t pid, const char *name, const char *shlib_regex, void *baton); +typedef nub_size_t (*DNBCallbackCopyExecutableImageInfos)(nub_process_t pid, struct DNBExecutableImageInfo **image_infos, nub_bool_t only_changed, void *baton); +typedef void (*DNBCallbackLog)(void *baton, uint32_t flags, const char *format, va_list args); + +#endif // #ifndef __DNBDefs_h__ diff --git a/lldb/tools/debugserver/source/DNBError.cpp b/lldb/tools/debugserver/source/DNBError.cpp new file mode 100644 index 00000000000..8f6294bfb51 --- /dev/null +++ b/lldb/tools/debugserver/source/DNBError.cpp @@ -0,0 +1,108 @@ +//===-- DNBError.cpp --------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/26/07. +// +//===----------------------------------------------------------------------===// + +#include "DNBError.h" +#include "CFString.h" +#include "DNBLog.h" +#include "PThreadMutex.h" + +#if defined (__arm__) +#include <SpringBoardServices/SpringBoardServer.h> +#endif + +const char * +DNBError::AsString() const +{ + if (Success()) + return NULL; + + if (m_str.empty()) + { + const char *s = NULL; + switch (m_flavor) + { + case MachKernel: + s = ::mach_error_string (m_err); + break; + + case POSIX: + s = ::strerror (m_err); + break; + +#if defined (__arm__) + case SpringBoard: + { + CFStringRef statusStr = SBSApplicationLaunchingErrorString (m_err); + if (CFString::UTF8 (statusStr, m_str) == NULL) + m_str.clear(); + } + break; +#endif + default: + break; + } + if (s) + m_str.assign(s); + } + if (m_str.empty()) + return NULL; + return m_str.c_str(); +} + +void +DNBError::LogThreadedIfError(const char *format, ...) const +{ + if (Fail()) + { + char *arg_msg = NULL; + va_list args; + va_start (args, format); + ::vasprintf (&arg_msg, format, args); + va_end (args); + + if (arg_msg != NULL) + { + const char *err_str = AsString(); + if (err_str == NULL) + err_str = "???"; + DNBLogThreaded ("error: %s err = %s (0x%8.8x)", arg_msg, err_str, m_err); + free (arg_msg); + } + } +} + +void +DNBError::LogThreaded(const char *format, ...) const +{ + char *arg_msg = NULL; + va_list args; + va_start (args, format); + ::vasprintf (&arg_msg, format, args); + va_end (args); + + if (arg_msg != NULL) + { + if (Fail()) + { + const char *err_str = AsString(); + if (err_str == NULL) + err_str = "???"; + DNBLogThreaded ("error: %s err = %s (0x%8.8x)", arg_msg, err_str, m_err); + } + else + { + DNBLogThreaded ("%s err = 0x%8.8x", arg_msg, m_err); + } + free (arg_msg); + } +} diff --git a/lldb/tools/debugserver/source/DNBError.h b/lldb/tools/debugserver/source/DNBError.h new file mode 100644 index 00000000000..10c6638c737 --- /dev/null +++ b/lldb/tools/debugserver/source/DNBError.h @@ -0,0 +1,96 @@ +//===-- DNBError.h ----------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/26/07. +// +//===----------------------------------------------------------------------===// + +#ifndef __DNBError_h__ +#define __DNBError_h__ + +#include <errno.h> +#include <mach/mach.h> +#include <stdio.h> +#include <string> + +class DNBError +{ +public: + typedef uint32_t ValueType; + typedef enum + { + Generic = 0, + MachKernel, + POSIX +#if defined (__arm__) + , SpringBoard +#endif + } FlavorType; + + explicit DNBError( ValueType err = 0, + FlavorType flavor = Generic) : + m_err(err), + m_flavor(flavor) + { + } + + const char * AsString() const; + void Clear() { m_err = 0; m_flavor = Generic; m_str.clear(); } + ValueType Error() const { return m_err; } + FlavorType Flavor() const { return m_flavor; } + + ValueType operator = (kern_return_t err) + { + m_err = err; + m_flavor = MachKernel; + m_str.clear(); + return m_err; + } + + void SetError(kern_return_t err) + { + m_err = err; + m_flavor = MachKernel; + m_str.clear(); + } + + void SetErrorToErrno() + { + m_err = errno; + m_flavor = POSIX; + m_str.clear(); + } + + void SetError(ValueType err, FlavorType flavor) + { + m_err = err; + m_flavor = flavor; + m_str.clear(); + } + + // Generic errors can set their own string values + void SetErrorString(const char *err_str) + { + if (err_str && err_str[0]) + m_str = err_str; + else + m_str.clear(); + } + bool Success() const { return m_err == 0; } + bool Fail() const { return m_err != 0; } + void LogThreadedIfError(const char *format, ...) const; + void LogThreaded(const char *format, ...) const; +protected: + ValueType m_err; + FlavorType m_flavor; + mutable std::string m_str; +}; + + +#endif // #ifndef __DNBError_h__ diff --git a/lldb/tools/debugserver/source/DNBLog.cpp b/lldb/tools/debugserver/source/DNBLog.cpp new file mode 100644 index 00000000000..d99e415f37e --- /dev/null +++ b/lldb/tools/debugserver/source/DNBLog.cpp @@ -0,0 +1,309 @@ +//===-- DNBLog.cpp ----------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/18/07. +// +//===----------------------------------------------------------------------===// + +#include "DNBLog.h" + +static int g_debug = 0; +static int g_verbose = 0; + +#if defined (DNBLOG_ENABLED) + +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <unistd.h> +#include <mach/mach.h> +#include <pthread.h> +#include "PThreadMutex.h" + +uint32_t g_log_bits = 0; +static DNBCallbackLog g_log_callback = NULL; +static void *g_log_baton = NULL; + + +int +DNBLogGetDebug () +{ + return g_debug; +} + + +void +DNBLogSetDebug (int g) +{ + g_debug = g; +} + +int +DNBLogGetVerbose () +{ + return g_verbose; +} + +void +DNBLogSetVerbose (int v) +{ + g_verbose = v; +} + +bool +DNBLogCheckLogBit (uint32_t bit) +{ + return (g_log_bits & bit) != 0; +} + +uint32_t +DNBLogSetLogMask (uint32_t mask) +{ + uint32_t old = g_log_bits; + g_log_bits = mask; + return old; +} + +uint32_t +DNBLogGetLogMask () +{ + return g_log_bits; +} + +void +DNBLogSetLogCallback (DNBCallbackLog callback, void *baton) +{ + g_log_callback = callback; + g_log_baton = baton; +} + +bool +DNBLogEnabled () +{ + return g_log_callback != NULL; +} + +static inline void +_DNBLogVAPrintf(uint32_t flags, const char *format, va_list args) +{ + if (g_log_callback) + g_log_callback(g_log_baton, flags, format, args); +} + +void +_DNBLog(uint32_t flags, const char *format, ...) +{ + va_list args; + va_start (args, format); + _DNBLogVAPrintf(flags, format, args); + va_end (args); +} + +//---------------------------------------------------------------------- +// Print debug strings if and only if the global g_debug is set to +// a non-zero value. +//---------------------------------------------------------------------- +void +_DNBLogDebug (const char *format, ...) +{ + if (DNBLogEnabled () && g_debug) + { + va_list args; + va_start (args, format); + _DNBLogVAPrintf(DNBLOG_FLAG_DEBUG, format, args); + va_end (args); + } +} + + +//---------------------------------------------------------------------- +// Print debug strings if and only if the global g_debug is set to +// a non-zero value. +//---------------------------------------------------------------------- +void +_DNBLogDebugVerbose (const char *format, ...) +{ + if (DNBLogEnabled () && g_debug && g_verbose) + { + va_list args; + va_start (args, format); + _DNBLogVAPrintf(DNBLOG_FLAG_DEBUG | DNBLOG_FLAG_VERBOSE, format, args); + va_end (args); + } +} + + +static pthread_mutex_t * +GetLogThreadedMutex() +{ + static PThreadMutex g_LogThreadedMutex(PTHREAD_MUTEX_RECURSIVE); + return g_LogThreadedMutex.Mutex(); +} +static uint32_t g_message_id = 0; + +//---------------------------------------------------------------------- +// Prefix the formatted log string with process and thread IDs and +// suffix it with a newline. +//---------------------------------------------------------------------- +void +_DNBLogThreaded (const char *format, ...) +{ + if (DNBLogEnabled ()) + { + PTHREAD_MUTEX_LOCKER(locker, GetLogThreadedMutex()); + + char *arg_msg = NULL; + va_list args; + va_start (args, format); + ::vasprintf (&arg_msg, format, args); + va_end (args); + + if (arg_msg != NULL) + { + _DNBLog (DNBLOG_FLAG_THREADED, "%u [%4.4x/%4.4x]: %s", ++g_message_id, getpid(), mach_thread_self(), arg_msg); + free (arg_msg); + } + } +} + +//---------------------------------------------------------------------- +// Prefix the formatted log string with process and thread IDs and +// suffix it with a newline. +//---------------------------------------------------------------------- +void +_DNBLogThreadedIf (uint32_t log_bit, const char *format, ...) +{ + if (DNBLogEnabled () && (log_bit & g_log_bits) == log_bit) + { + PTHREAD_MUTEX_LOCKER(locker, GetLogThreadedMutex()); + + char *arg_msg = NULL; + va_list args; + va_start (args, format); + ::vasprintf (&arg_msg, format, args); + va_end (args); + + if (arg_msg != NULL) + { + _DNBLog (DNBLOG_FLAG_THREADED, "%u [%4.4x/%4.4x]: %s", ++g_message_id, getpid(), mach_thread_self(), arg_msg); + free (arg_msg); + } + } +} + + + +//---------------------------------------------------------------------- +// Printing of errors that are not fatal. +//---------------------------------------------------------------------- +void +_DNBLogError (const char *format, ...) +{ + if (DNBLogEnabled ()) + { + char *arg_msg = NULL; + va_list args; + va_start (args, format); + ::vasprintf (&arg_msg, format, args); + va_end (args); + + if (arg_msg != NULL) + { + _DNBLog (DNBLOG_FLAG_ERROR, "error: %s", arg_msg); + free (arg_msg); + } + } +} + +//---------------------------------------------------------------------- +// Printing of errors that ARE fatal. Exit with ERR exit code +// immediately. +//---------------------------------------------------------------------- +void +_DNBLogFatalError (int err, const char *format, ...) +{ + if (DNBLogEnabled ()) + { + char *arg_msg = NULL; + va_list args; + va_start (args, format); + ::vasprintf (&arg_msg, format, args); + va_end (args); + + if (arg_msg != NULL) + { + _DNBLog (DNBLOG_FLAG_ERROR | DNBLOG_FLAG_FATAL, "error: %s", arg_msg); + free (arg_msg); + } + ::exit (err); + } +} + + +//---------------------------------------------------------------------- +// Printing of warnings that are not fatal only if verbose mode is +// enabled. +//---------------------------------------------------------------------- +void +_DNBLogVerbose (const char *format, ...) +{ + if (DNBLogEnabled () && g_verbose) + { + va_list args; + va_start (args, format); + _DNBLogVAPrintf(DNBLOG_FLAG_VERBOSE, format, args); + va_end (args); + } +} + +//---------------------------------------------------------------------- +// Printing of warnings that are not fatal only if verbose mode is +// enabled. +//---------------------------------------------------------------------- +void +_DNBLogWarningVerbose (const char *format, ...) +{ + if (DNBLogEnabled () && g_verbose) + { + char *arg_msg = NULL; + va_list args; + va_start (args, format); + ::vasprintf (&arg_msg, format, args); + va_end (args); + + if (arg_msg != NULL) + { + _DNBLog (DNBLOG_FLAG_WARNING | DNBLOG_FLAG_VERBOSE, "warning: %s", arg_msg); + free (arg_msg); + } + } +} +//---------------------------------------------------------------------- +// Printing of warnings that are not fatal. +//---------------------------------------------------------------------- +void +_DNBLogWarning (const char *format, ...) +{ + if (DNBLogEnabled ()) + { + char *arg_msg = NULL; + va_list args; + va_start (args, format); + ::vasprintf (&arg_msg, format, args); + va_end (args); + + if (arg_msg != NULL) + { + _DNBLog (DNBLOG_FLAG_WARNING, "warning: %s", arg_msg); + free (arg_msg); + } + } +} + +#endif diff --git a/lldb/tools/debugserver/source/DNBLog.h b/lldb/tools/debugserver/source/DNBLog.h new file mode 100644 index 00000000000..76ce0c55d37 --- /dev/null +++ b/lldb/tools/debugserver/source/DNBLog.h @@ -0,0 +1,96 @@ +//===-- DNBLog.h ------------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/18/07. +// +//===----------------------------------------------------------------------===// + +#ifndef __DNBLog_h__ +#define __DNBLog_h__ + +#include <stdio.h> +#include <stdint.h> +#include "DNBDefs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Flags that get filled in automatically before calling the log callback function +#define DNBLOG_FLAG_FATAL (1u << 0) +#define DNBLOG_FLAG_ERROR (1u << 1) +#define DNBLOG_FLAG_WARNING (1u << 2) +#define DNBLOG_FLAG_DEBUG (1u << 3) +#define DNBLOG_FLAG_VERBOSE (1u << 4) +#define DNBLOG_FLAG_THREADED (1u << 5) + +#define DNBLOG_ENABLED + +#if defined (DNBLOG_ENABLED) + +#define DNB_EXPORT __attribute__((visibility("default"))) + +void _DNBLog(uint32_t flags, const char *format, ...) DNB_EXPORT; +void _DNBLogDebug (const char *fmt, ...) DNB_EXPORT; +void _DNBLogDebugVerbose (const char *fmt, ...) DNB_EXPORT; +void _DNBLogThreaded (const char *fmt, ...) DNB_EXPORT; +void _DNBLogThreadedIf (uint32_t mask, const char *fmt, ...) DNB_EXPORT; +void _DNBLogError (const char *fmt, ...) DNB_EXPORT; +void _DNBLogFatalError (int err, const char *fmt, ...) DNB_EXPORT; +void _DNBLogVerbose (const char *fmt, ...) DNB_EXPORT; +void _DNBLogWarning (const char *fmt, ...) DNB_EXPORT; +void _DNBLogWarningVerbose (const char *fmt, ...) DNB_EXPORT; +bool DNBLogCheckLogBit (uint32_t bit) DNB_EXPORT; +uint32_t DNBLogSetLogMask (uint32_t mask) DNB_EXPORT; +uint32_t DNBLogGetLogMask () DNB_EXPORT; +void DNBLogSetLogCallback (DNBCallbackLog callback, void *baton) DNB_EXPORT; +bool DNBLogEnabled () DNB_EXPORT; +int DNBLogGetDebug () DNB_EXPORT; +void DNBLogSetDebug (int g) DNB_EXPORT; +int DNBLogGetVerbose () DNB_EXPORT; +void DNBLogSetVerbose (int g) DNB_EXPORT; + +#define DNBLog(fmt, ...) do { if (DNBLogEnabled()) { _DNBLog(0, fmt, ## __VA_ARGS__); } } while (0) +#define DNBLogDebug(fmt, ...) do { if (DNBLogEnabled()) { _DNBLogDebug(fmt, ## __VA_ARGS__); } } while (0) +#define DNBLogDebugVerbose(fmt, ...) do { if (DNBLogEnabled()) { _DNBLogDebugVerbose(fmt, ## __VA_ARGS__); } } while (0) +#define DNBLogThreaded(fmt, ...) do { if (DNBLogEnabled()) { _DNBLogThreaded(fmt, ## __VA_ARGS__); } } while (0) +#define DNBLogThreadedIf(mask, fmt, ...) do { if (DNBLogEnabled()) { _DNBLogThreadedIf(mask, fmt, ## __VA_ARGS__); } } while (0) +#define DNBLogError(fmt, ...) do { if (DNBLogEnabled()) { _DNBLogError(fmt, ## __VA_ARGS__); } } while (0) +#define DNBLogFatalError(err, fmt, ...) do { if (DNBLogEnabled()) { _DNBLogFatalError(err, fmt, ## __VA_ARGS__); } } while (0) +#define DNBLogVerbose(fmt, ...) do { if (DNBLogEnabled()) { _DNBLogVerbose(fmt, ## __VA_ARGS__); } } while (0) +#define DNBLogWarning(fmt, ...) do { if (DNBLogEnabled()) { _DNBLogWarning(fmt, ## __VA_ARGS__); } } while (0) +#define DNBLogWarningVerbose(fmt, ...) do { if (DNBLogEnabled()) { _DNBLogWarningVerbose(fmt, ## __VA_ARGS__); } } while (0) + +#else // #if defined(DNBLOG_ENABLED) + +#define DNBLogDebug(...) ((void)0) +#define DNBLogDebugVerbose(...) ((void)0) +#define DNBLogThreaded(...) ((void)0) +#define DNBLogThreadedIf(...) ((void)0) +#define DNBLogError(...) ((void)0) +#define DNBLogFatalError(...) ((void)0) +#define DNBLogVerbose(...) ((void)0) +#define DNBLogWarning(...) ((void)0) +#define DNBLogWarningVerbose(...) ((void)0) +#define DNBLogGetLogFile() ((FILE *)NULL) +#define DNBLogSetLogFile(f) ((void)0) +#define DNBLogCheckLogBit(bit) ((bool)false) +#define DNBLogSetLogMask(mask) ((uint32_t)0u) +#define DNBLogGetLogMask() ((uint32_t)0u) +#define DNBLogToASL() ((void)0) +#define DNBLogToFile() ((void)0) +#define DNBLogCloseLogFile() ((void)0) + +#endif // #else defined(DNBLOG_ENABLED) + +#ifdef __cplusplus +} +#endif + +#endif // #ifndef __DNBLog_h__ diff --git a/lldb/tools/debugserver/source/DNBRegisterInfo.cpp b/lldb/tools/debugserver/source/DNBRegisterInfo.cpp new file mode 100644 index 00000000000..a8f09bc8784 --- /dev/null +++ b/lldb/tools/debugserver/source/DNBRegisterInfo.cpp @@ -0,0 +1,220 @@ +//===-- DNBRegisterInfo.cpp -------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 8/3/07. +// +//===----------------------------------------------------------------------===// + +#include "DNBRegisterInfo.h" +#include "DNBLog.h" +#include <string.h> + +DNBRegisterValueClass::DNBRegisterValueClass(const DNBRegisterInfo *regInfo) +{ + Clear(); + if (regInfo) + info = *regInfo; +} + +void +DNBRegisterValueClass::Clear() +{ + memset(&info, 0, sizeof(DNBRegisterInfo)); + memset(&value, 0, sizeof(value)); +} + +bool +DNBRegisterValueClass::IsValid() const +{ + return + info.name != NULL && + info.type != InvalidRegType && + info.size > 0 && info.size <= sizeof(value); +} + +#define PRINT_COMMA_SEPARATOR do { if (pos < end) { if (i > 0) { strncpy(pos, ", ", end - pos); pos += 2; } } } while (0) + +void +DNBRegisterValueClass::Dump(const char *pre, const char *post) const +{ + if (info.name != NULL) + { + int i; + char str[1024]; + char *pos; + char *end = str + sizeof(str); + if (info.format == Hex) + { + switch (info.size) + { + case 0: snprintf(str, sizeof(str), "%s", "error: invalid register size of zero."); break; + case 1: snprintf(str, sizeof(str), "0x%2.2x", value.uint8); break; + case 2: snprintf(str, sizeof(str), "0x%4.4x", value.uint16); break; + case 4: snprintf(str, sizeof(str), "0x%8.8x", value.uint32); break; + case 8: snprintf(str, sizeof(str), "0x%16.16llx", value.uint64); break; + case 16: snprintf(str, sizeof(str), "0x%16.16llx%16.16llx", value.v_uint64[0], value.v_uint64[1]); break; + default: + strncpy(str, "0x", 3); + pos = str + 2; + for (i=0; i<info.size; ++i) + { + if (pos < end) + pos += snprintf(pos, end - pos, "%2.2x", (uint32_t)value.v_uint8[i]); + } + break; + } + } + else + { + switch (info.type) + { + case Uint: + switch (info.size) + { + case 1: snprintf(str, sizeof(str), "%u", value.uint8); break; + case 2: snprintf(str, sizeof(str), "%u", value.uint16); break; + case 4: snprintf(str, sizeof(str), "%u", value.uint32); break; + case 8: snprintf(str, sizeof(str), "%llu", value.uint64); break; + default: snprintf(str, sizeof(str), "error: unsupported uint byte size %d.", info.size); break; + } + break; + + case Sint: + switch (info.size) + { + case 1: snprintf(str, sizeof(str), "%d", value.sint8); break; + case 2: snprintf(str, sizeof(str), "%d", value.sint16); break; + case 4: snprintf(str, sizeof(str), "%d", value.sint32); break; + case 8: snprintf(str, sizeof(str), "%lld", value.sint64); break; + default: snprintf(str, sizeof(str), "error: unsupported sint byte size %d.", info.size); break; + } + break; + + case IEEE754: + switch (info.size) + { + case 4: snprintf(str, sizeof(str), "%f", value.float32); break; + case 8: snprintf(str, sizeof(str), "%g", value.float64); break; + default: snprintf(str, sizeof(str), "error: unsupported float byte size %d.", info.size); break; + } + break; + + case Vector: + if (info.size > 0) + { + switch (info.format) + { + case VectorOfSInt8: + snprintf(str, sizeof(str), "%s", "sint8 { "); + pos = str + strlen(str); + for (i=0; i<info.size; ++i) + { + PRINT_COMMA_SEPARATOR; + if (pos < end) + pos += snprintf(pos, end - pos, "%d", (int32_t)value.v_sint8[i]); + } + strncat(str, " }", sizeof(str)); + break; + + default: + DNBLogError("unsupported vector format %d, defaulting to hex bytes.", info.format); + case VectorOfUInt8: + snprintf(str, sizeof(str), "%s", "uint8 { "); + pos = str + strlen(str); + for (i=0; i<info.size; ++i) + { + PRINT_COMMA_SEPARATOR; + if (pos < end) + pos += snprintf(pos, end - pos, "%u", (uint32_t)value.v_uint8[i]); + } + break; + + case VectorOfSInt16: + snprintf(str, sizeof(str), "%s", "sint16 { "); + pos = str + strlen(str); + for (i=0; i<info.size/2; ++i) + { + PRINT_COMMA_SEPARATOR; + if (pos < end) + pos += snprintf(pos, end - pos, "%d", (int32_t)value.v_sint16[i]); + } + break; + + case VectorOfUInt16: + snprintf(str, sizeof(str), "%s", "uint16 { "); + pos = str + strlen(str); + for (i=0; i<info.size/2; ++i) + { + PRINT_COMMA_SEPARATOR; + if (pos < end) + pos += snprintf(pos, end - pos, "%u", (uint32_t)value.v_uint16[i]); + } + break; + + case VectorOfSInt32: + snprintf(str, sizeof(str), "%s", "sint32 { "); + pos = str + strlen(str); + for (i=0; i<info.size/4; ++i) + { + PRINT_COMMA_SEPARATOR; + if (pos < end) + pos += snprintf(pos, end - pos, "%d", (int32_t)value.v_sint32[i]); + } + break; + + case VectorOfUInt32: + snprintf(str, sizeof(str), "%s", "uint32 { "); + pos = str + strlen(str); + for (i=0; i<info.size/4; ++i) + { + PRINT_COMMA_SEPARATOR; + if (pos < end) + pos += snprintf(pos, end - pos, "%u", (uint32_t)value.v_uint32[i]); + } + break; + + case VectorOfFloat32: + snprintf(str, sizeof(str), "%s", "float32 { "); + pos = str + strlen(str); + for (i=0; i<info.size/4; ++i) + { + PRINT_COMMA_SEPARATOR; + if (pos < end) + pos += snprintf(pos, end - pos, "%f", value.v_float32[i]); + } + break; + + case VectorOfUInt128: + snprintf(str, sizeof(str), "%s", "uint128 { "); + pos = str + strlen(str); + for (i=0; i<info.size/16; ++i) + { + PRINT_COMMA_SEPARATOR; + if (pos < end) + pos += snprintf(pos, end - pos, "0x%16.16llx%16.16llx", value.v_uint64[i], value.v_uint64[i+1]); + } + break; + } + strncat(str, " }", sizeof(str)); + } + else + { + snprintf(str, sizeof(str), "error: unsupported vector size %d.", info.size); + } + break; + + default: + snprintf(str, sizeof(str), "error: unsupported register type %d.", info.type); + break; + } + } + + DNBLog("%s%4s = %s%s", pre ? pre : "", info.name, str, post ? post : ""); + } +} diff --git a/lldb/tools/debugserver/source/DNBRegisterInfo.h b/lldb/tools/debugserver/source/DNBRegisterInfo.h new file mode 100644 index 00000000000..666c397e0b5 --- /dev/null +++ b/lldb/tools/debugserver/source/DNBRegisterInfo.h @@ -0,0 +1,31 @@ +//===-- DNBRegisterInfo.h ---------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 8/3/07. +// +//===----------------------------------------------------------------------===// + +#ifndef __DNBRegisterInfo_h__ +#define __DNBRegisterInfo_h__ + +#include <stdint.h> +#include <stdio.h> +#include "DNBDefs.h" + +struct DNBRegisterValueClass : public DNBRegisterValue +{ +#ifdef __cplusplus + DNBRegisterValueClass(const DNBRegisterInfo *regInfo = NULL); + void Clear(); + void Dump(const char *pre, const char *post) const; + bool IsValid() const; +#endif +}; + +#endif diff --git a/lldb/tools/debugserver/source/DNBRuntimeAction.h b/lldb/tools/debugserver/source/DNBRuntimeAction.h new file mode 100644 index 00000000000..d77bda8c604 --- /dev/null +++ b/lldb/tools/debugserver/source/DNBRuntimeAction.h @@ -0,0 +1,25 @@ +//===-- DNBRuntimeAction.h --------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 10/8/07. +// +//===----------------------------------------------------------------------===// + +#ifndef __DNBRuntimeAction_h__ +#define __DNBRuntimeAction_h__ + +class DNBRuntimeAction +{ + virtual void Initialize (nub_process_t pid) = 0; + virtual void ProcessStateChanged (nub_state_t state) = 0; + virtual void SharedLibraryStateChanged (DNBExecutableImageInfo *image_infos, nub_size_t num_image_infos) = 0; +}; + + +#endif // #ifndef __DNBRuntimeAction_h__ diff --git a/lldb/tools/debugserver/source/DNBThreadResumeActions.cpp b/lldb/tools/debugserver/source/DNBThreadResumeActions.cpp new file mode 100644 index 00000000000..b50dd061784 --- /dev/null +++ b/lldb/tools/debugserver/source/DNBThreadResumeActions.cpp @@ -0,0 +1,116 @@ +//===-- DNBThreadResumeActions.cpp ------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 03/13/2010 +// +//===----------------------------------------------------------------------===// + +#include "DNBThreadResumeActions.h" + +DNBThreadResumeActions::DNBThreadResumeActions() : + m_actions (), + m_signal_handled () +{ +} + +DNBThreadResumeActions::DNBThreadResumeActions (const DNBThreadResumeAction *actions, size_t num_actions) : + m_actions (), + m_signal_handled () +{ + if (actions && num_actions) + { + m_actions.assign (actions, actions + num_actions); + m_signal_handled.assign (num_actions, false); + } +} + +DNBThreadResumeActions::DNBThreadResumeActions (nub_state_t default_action, int signal) : + m_actions(), + m_signal_handled () +{ + SetDefaultThreadActionIfNeeded (default_action, signal); +} + +void +DNBThreadResumeActions::Append (const DNBThreadResumeAction &action) +{ + m_actions.push_back (action); + m_signal_handled.push_back (false); +} + +void +DNBThreadResumeActions::AppendAction +( + nub_thread_t tid, + nub_state_t state, + int signal, + nub_addr_t addr +) +{ + DNBThreadResumeAction action = { tid, state, signal, addr }; + Append (action); +} + + +const DNBThreadResumeAction * +DNBThreadResumeActions::GetActionForThread (nub_thread_t tid, bool default_ok) const +{ + const size_t num_actions = m_actions.size(); + for (size_t i=0; i<num_actions; ++i) + { + if (m_actions[i].tid == tid) + return &m_actions[i]; + } + if (default_ok && tid != INVALID_NUB_THREAD) + return GetActionForThread (INVALID_NUB_THREAD, false); + return NULL; +} + +size_t +DNBThreadResumeActions::NumActionsWithState (nub_state_t state) const +{ + size_t count = 0; + const size_t num_actions = m_actions.size(); + for (size_t i=0; i<num_actions; ++i) + { + if (m_actions[i].state == state) + ++count; + } + return count; +} + + +bool +DNBThreadResumeActions::SetDefaultThreadActionIfNeeded (nub_state_t action, int signal) +{ + if (GetActionForThread (INVALID_NUB_THREAD, true) == NULL) + { + // There isn't a default action so we do need to set it. + DNBThreadResumeAction default_action = {INVALID_NUB_THREAD, action, signal, INVALID_NUB_ADDRESS }; + m_actions.push_back (default_action); + m_signal_handled.push_back (false); + return true; // Return true as we did add the default action + } + return false; +} + + +void +DNBThreadResumeActions::SetSignalHandledForThread (nub_thread_t tid) const +{ + if (tid != INVALID_NUB_THREAD) + { + const size_t num_actions = m_actions.size(); + for (size_t i=0; i<num_actions; ++i) + { + if (m_actions[i].tid == tid) + m_signal_handled[i] = true; + } + } +} diff --git a/lldb/tools/debugserver/source/DNBThreadResumeActions.h b/lldb/tools/debugserver/source/DNBThreadResumeActions.h new file mode 100644 index 00000000000..f3fb8de6a82 --- /dev/null +++ b/lldb/tools/debugserver/source/DNBThreadResumeActions.h @@ -0,0 +1,95 @@ +//===-- DNBThreadResumeActions.h --------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 03/13/2010 +// +//===----------------------------------------------------------------------===// + + +#ifndef __DNBThreadResumeActions_h__ +#define __DNBThreadResumeActions_h__ + +#include <vector> + +#include "DNBDefs.h" + + +class DNBThreadResumeActions +{ +public: + DNBThreadResumeActions (); + + DNBThreadResumeActions (nub_state_t default_action, int signal); + + DNBThreadResumeActions (const DNBThreadResumeAction *actions, size_t num_actions); + + bool + IsEmpty() const + { + return m_actions.empty(); + } + + void + Append (const DNBThreadResumeAction &action); + + void + AppendAction (nub_thread_t tid, + nub_state_t state, + int signal = 0, + nub_addr_t addr = INVALID_NUB_ADDRESS); + + void + AppendResumeAll () + { + AppendAction (INVALID_NUB_THREAD, eStateRunning); + } + + void + AppendSuspendAll () + { + AppendAction (INVALID_NUB_THREAD, eStateStopped); + } + + void + AppendStepAll () + { + AppendAction (INVALID_NUB_THREAD, eStateStepping); + } + + const DNBThreadResumeAction * + GetActionForThread (nub_thread_t tid, bool default_ok) const; + + size_t + NumActionsWithState (nub_state_t state) const; + + bool + SetDefaultThreadActionIfNeeded (nub_state_t action, int signal); + + void + SetSignalHandledForThread (nub_thread_t tid) const; + + const DNBThreadResumeAction * + GetFirst() const + { + return m_actions.data(); + } + + size_t + GetSize () const + { + return m_actions.size(); + } + +protected: + std::vector<DNBThreadResumeAction> m_actions; + mutable std::vector<bool> m_signal_handled; +}; + + +#endif // #ifndef __DNBThreadResumeActions_h__ diff --git a/lldb/tools/debugserver/source/DNBTimer.h b/lldb/tools/debugserver/source/DNBTimer.h new file mode 100644 index 00000000000..a78b80f606d --- /dev/null +++ b/lldb/tools/debugserver/source/DNBTimer.h @@ -0,0 +1,162 @@ +//===-- DNBTimer.h ----------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 12/13/07. +// +//===----------------------------------------------------------------------===// + +#ifndef __DNBTimer_h__ +#define __DNBTimer_h__ + +#include <sys/time.h> +#include <stdint.h> +#include <memory> +#include "PThreadMutex.h" + +class DNBTimer +{ +public: + //------------------------------------------------------------------ + // Constructors and Destructors + //------------------------------------------------------------------ + DNBTimer (bool threadSafe) : + m_mutexAP() + { + if (threadSafe) + m_mutexAP.reset(new PThreadMutex(PTHREAD_MUTEX_RECURSIVE)); + Reset(); + } + + DNBTimer (const DNBTimer& rhs) : + m_mutexAP() + { + // Create a new mutex to make this timer thread safe as well if + // the timer we are copying is thread safe + if (rhs.IsThreadSafe()) + m_mutexAP.reset(new PThreadMutex(PTHREAD_MUTEX_RECURSIVE)); + m_timeval = rhs.m_timeval; + } + + DNBTimer& operator= (const DNBTimer& rhs) + { + // Create a new mutex to make this timer thread safe as well if + // the timer we are copying is thread safe + if (rhs.IsThreadSafe()) + m_mutexAP.reset(new PThreadMutex(PTHREAD_MUTEX_RECURSIVE)); + m_timeval = rhs.m_timeval; + return *this; + } + + ~DNBTimer () + { + } + + bool + IsThreadSafe() const + { + return m_mutexAP.get() != NULL; + } + //------------------------------------------------------------------ + // Reset the time value to now + //------------------------------------------------------------------ + void + Reset () + { + PTHREAD_MUTEX_LOCKER (locker, m_mutexAP.get()); + gettimeofday (&m_timeval, NULL); + } + //------------------------------------------------------------------ + // Get the total mircoseconds since Jan 1, 1970 + //------------------------------------------------------------------ + uint64_t + TotalMicroSeconds () const + { + PTHREAD_MUTEX_LOCKER (locker, m_mutexAP.get()); + return (uint64_t)(m_timeval.tv_sec) * 1000000ull + (uint64_t)m_timeval.tv_usec; + } + + void + GetTime (uint32_t& sec, uint32_t& usec) const + { + PTHREAD_MUTEX_LOCKER (locker, m_mutexAP.get()); + sec = m_timeval.tv_sec; + usec = m_timeval.tv_usec; + } + //------------------------------------------------------------------ + // Return the number of microseconds elapsed between now and the + // m_timeval + //------------------------------------------------------------------ + uint64_t + ElapsedMicroSeconds (bool update) + { + PTHREAD_MUTEX_LOCKER (locker, m_mutexAP.get()); + struct timeval now; + gettimeofday (&now, NULL); + uint64_t now_usec = (uint64_t)(now.tv_sec) * 1000000ull + (uint64_t)now.tv_usec; + uint64_t this_usec = (uint64_t)(m_timeval.tv_sec) * 1000000ull + (uint64_t)m_timeval.tv_usec; + uint64_t elapsed = now_usec - this_usec; + // Update the timer time value if requeseted + if (update) + m_timeval = now; + return elapsed; + } + + static uint64_t GetTimeOfDay() + { + struct timeval now; + gettimeofday (&now, NULL); + uint64_t now_usec = (uint64_t)(now.tv_sec) * 1000000ull + (uint64_t)now.tv_usec; + return now_usec; + } + + static void OffsetTimeOfDay (struct timespec* ts, __darwin_time_t sec_offset = 0, long nsec_offset = 0) + { + if (ts == NULL) + return; + // Get the current time in a timeval structure + struct timeval now; + gettimeofday (&now, NULL); + // Morph it into a timespec + TIMEVAL_TO_TIMESPEC(&now, ts); + // Offset the timespec if requested + if (sec_offset != 0 || nsec_offset != 0) + { + // Offset the nano seconds + ts->tv_nsec += nsec_offset; + // Offset the seconds taking into account a nano-second overflow + ts->tv_sec = ts->tv_sec + ts->tv_nsec / 1000000000 + sec_offset; + // Trim the nanoseconds back there was an overflow + ts->tv_nsec = ts->tv_nsec % 1000000000; + } + } + static bool TimeOfDayLaterThan (struct timespec &ts) + { + struct timespec now; + OffsetTimeOfDay(&now); + if (now.tv_sec > ts.tv_sec) + return true; + else if (now.tv_sec < ts.tv_sec) + return false; + else + { + if (now.tv_nsec > ts.tv_nsec) + return true; + else + return false; + } + } +protected: + //------------------------------------------------------------------ + // Classes that inherit from DNBTimer can see and modify these + //------------------------------------------------------------------ + std::auto_ptr<PThreadMutex> m_mutexAP; + struct timeval m_timeval; +}; + +#endif // #ifndef __DNBTimer_h__ diff --git a/lldb/tools/debugserver/source/FunctionProfiler.cpp b/lldb/tools/debugserver/source/FunctionProfiler.cpp new file mode 100644 index 00000000000..2fa57d3a770 --- /dev/null +++ b/lldb/tools/debugserver/source/FunctionProfiler.cpp @@ -0,0 +1,288 @@ +//===-- FunctionProfiler.cpp ------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 10/8/08. +// +//===----------------------------------------------------------------------===// + +#include "FunctionProfiler.h" + +// C Includes +// C++ Includes +// Other libraries and framework includes +#include "DNB.h" + +// Project includes + +//---------------------------------------------------------------------- +// FunctionProfiler constructor +//---------------------------------------------------------------------- +FunctionProfiler::FunctionProfiler(nub_addr_t start_addr, nub_addr_t stop_addr) : + m_pid(INVALID_NUB_PROCESS), + m_start_addr(start_addr), + m_stop_addr(stop_addr), + m_start_break_id(INVALID_NUB_BREAK_ID), + m_stop_break_id(INVALID_NUB_BREAK_ID), + m_func_entered_count(0), + m_last_pc(0), + m_last_flags(0), + m_consecutive_opcode_count(0), + m_total_opcode_count(0) +{ +} + + +FunctionProfiler::~FunctionProfiler() +{ + Clear(); +} + + +void +FunctionProfiler::Clear() +{ + if (m_pid != INVALID_NUB_PROCESS) + { + if (m_start_break_id != INVALID_NUB_BREAK_ID) + DNBBreakpointClear(m_pid, m_start_break_id); + if (m_stop_break_id != INVALID_NUB_BREAK_ID) + DNBBreakpointClear(m_pid, m_stop_break_id); + } + m_start_break_id = INVALID_NUB_BREAK_ID; + m_stop_break_id = INVALID_NUB_BREAK_ID; + m_func_entered_count = 0; + m_last_pc = 0; + m_last_flags = 0; + m_consecutive_opcode_count = 0; +} + +void +FunctionProfiler::Initialize(nub_process_t pid) +{ + //printf("FunctionProfiler::%s(0x%4.4x)\n", __FUNCTION__, pid); + Clear(); + m_pid = pid; +} + +#include "DNBDataRef.h" + +void +FunctionProfiler::SetBreakpoints() +{ +#if defined (__i386__) + nub_size_t bp_opcode_size = 1; +#elif defined (__powerpc__) || defined (__ppc__) + nub_size_t bp_opcode_size = 4; +#endif + if (m_start_addr != INVALID_NUB_ADDRESS && !NUB_BREAK_ID_IS_VALID(m_start_break_id)) + { +#if defined (__arm__) + m_start_break_id = DNBBreakpointSet(m_pid, m_start_addr & 0xFFFFFFFEu, m_start_addr & 1 ? 2 : 4, false); +#else + m_start_break_id = DNBBreakpointSet(m_pid, m_start_addr, bp_opcode_size, false); +#endif + if (NUB_BREAK_ID_IS_VALID(m_start_break_id)) + DNBBreakpointSetCallback(m_pid, m_start_break_id, FunctionProfiler::BreakpointHitCallback, this); + } + if (m_stop_addr != INVALID_NUB_ADDRESS && !NUB_BREAK_ID_IS_VALID(m_stop_break_id)) + { +#if defined (__arm__) + m_stop_break_id = DNBBreakpointSet(m_pid, m_stop_addr & 0xFFFFFFFEu, m_stop_addr & 1 ? 2 : 4, false); +#else + m_stop_break_id = DNBBreakpointSet(m_pid, m_stop_addr, bp_opcode_size, false); +#endif + if (NUB_BREAK_ID_IS_VALID(m_stop_break_id)) + DNBBreakpointSetCallback(m_pid, m_stop_break_id, FunctionProfiler::BreakpointHitCallback, this); + } +} + +nub_bool_t +FunctionProfiler::BreakpointHitCallback(nub_process_t pid, nub_thread_t tid, nub_break_t breakID, void *baton) +{ + printf("FunctionProfiler::%s(pid = %4.4x, tid = %4.4x, breakID = %u, baton = %p)\n", __FUNCTION__, pid, tid, breakID, baton); + return ((FunctionProfiler*) baton)->BreakpointHit(pid, tid, breakID); +} + +nub_bool_t +FunctionProfiler::BreakpointHit(nub_process_t pid, nub_thread_t tid, nub_break_t breakID) +{ + printf("FunctionProfiler::%s(pid = %4.4x, tid = %4.4x, breakID = %u)\n", __FUNCTION__, pid, tid, breakID); + if (breakID == m_start_break_id) + { + m_func_entered_count++; + printf("FunctionProfiler::%s(pid = %4.4x, tid = %4.4x, breakID = %u) START breakpoint hit (%u)\n", __FUNCTION__, pid, tid, breakID, m_func_entered_count); + } + else if (breakID == m_stop_break_id) + { + if (m_func_entered_count > 0) + m_func_entered_count--; + printf("FunctionProfiler::%s(pid = %4.4x, tid = %4.4x, breakID = %u) STOP breakpoint hit (%u)\n", __FUNCTION__, pid, tid, breakID, m_func_entered_count); + } + return true; +} + +void +FunctionProfiler::ProcessStateChanged(nub_state_t state) +{ +// printf("FunctionProfiler::%s(%s)\n", __FUNCTION__, DNBStateAsString(state)); + + switch (state) + { + case eStateInvalid: + case eStateUnloaded: + case eStateAttaching: + case eStateLaunching: + break; + + case eStateDetached: + case eStateExited: + // No sense is clearing out breakpoints if our process has exited... + m_start_break_id = INVALID_NUB_BREAK_ID; + m_stop_break_id = INVALID_NUB_BREAK_ID; + printf("[0x%8.8x - 0x%8.8x) executed %u total opcodes.\n", m_total_opcode_count); + break; + + case eStateStopped: + // Keep trying find dyld each time we stop until we do + if (!NUB_BREAK_ID_IS_VALID(m_start_break_id)) + SetBreakpoints(); + + if (ShouldStepProcess()) + { + + // TODO: do logging/tracing here + nub_thread_t tid = DNBProcessGetCurrentThread(m_pid); + DNBRegisterValue reg; + m_total_opcode_count++; + + if (DNBThreadGetRegisterValueByID(m_pid, tid, REGISTER_SET_GENERIC, GENERIC_REGNUM_PC, ®)) + { + const nub_addr_t pc = reg.value.uint32; + +#if defined (__i386__) + uint8_t buf[16]; + uint32_t bytes_read = DNBProcessMemoryRead(m_pid, pc, 1, buf); + if (bytes_read == 1) + printf("0x%8.8x: %2.2x\n", pc, buf[0]); + else + printf("0x%8.8x: error: can't read opcode byte.\n", pc); + +// if (bytes_read > 0) +// { +// for (uint32_t i=0; i<bytes_read; ++i) +// { +// printf(" %2.2x", buf[i]); +// } +// } +// printf("\n"); + +#elif defined (__powerpc__) || defined (__ppc__) + + uint32_t opcode = 0; + if (DNBProcessMemoryRead(m_pid, pc, 4, &opcode) == 4) + { + printf("0x%8.8x: 0x%8.8x\n", pc, opcode); + } + +#elif defined (__arm__) + #define CPSR_T (1u << 5) + // Read the CPSR into flags + if (DNBThreadGetRegisterValueByID(m_pid, tid, REGISTER_SET_GENERIC, GENERIC_REGNUM_FLAGS, ®)) + { + const uint32_t flags = reg.value.uint32; + + const bool curr_pc_is_thumb = (flags & CPSR_T) != 0; // check the CPSR T bit + const bool last_pc_was_thumb = (m_last_flags & CPSR_T) != 0; // check the CPSR T bit + bool opcode_is_sequential = false; + + uint32_t opcode; + // Always read four bytes for the opcode + if (DNBProcessMemoryRead(m_pid, pc, 4, &opcode) == 4) + { + if (curr_pc_is_thumb) + { + // Trim off the high 16 bits if this is a 16 bit thumb instruction + if ((opcode & 0xe000) != 0xe000 || (opcode & 0x1800) == 0) + { + opcode &= 0xFFFFu; + printf("0x%8.8x: %4.4x Thumb\n", pc, opcode); + } + else + printf("0x%8.8x: %8.8x Thumb\n", pc, opcode); + } + else + printf("0x%8.8x: %8.8x ARM\n", pc, opcode); + } + + if (m_last_flags != 0 && curr_pc_is_thumb == last_pc_was_thumb) + { + if (curr_pc_is_thumb) + { + if (pc == m_last_pc + 2) + { + opcode_is_sequential = true; + } + else if (pc == m_last_pc + 4) + { + // Check for 32 bit thumb instruction... + uint16_t opcode16; + if (DNBProcessMemoryRead(m_pid, m_last_pc, 2, &opcode16) == 2) + { + if ((opcode16 & 0xe000) == 0xe000 && (opcode16 & 0x1800) != 0) + { + // Last opcode was a 32 bit thumb instruction... + opcode_is_sequential = true; + } + } + } + } + else + { + if (pc == m_last_pc + 4) + { + opcode_is_sequential = true; + } + } + } + + + if (opcode_is_sequential) + { + m_consecutive_opcode_count++; + } + else + { + if (m_consecutive_opcode_count > 0) + { + // printf(" x %u\n", m_consecutive_opcode_count); + } + m_consecutive_opcode_count = 1; + //printf("0x%8.8x: %-5s", pc, curr_pc_is_thumb ? "Thumb" : "ARM"); + //fflush(stdout); + } + m_last_flags = flags; + } +#else +#error undefined architecture +#endif + m_last_pc = pc; + } + } + break; + + case eStateRunning: + case eStateStepping: + case eStateCrashed: + case eStateSuspended: + break; + + default: + break; + } +} diff --git a/lldb/tools/debugserver/source/FunctionProfiler.h b/lldb/tools/debugserver/source/FunctionProfiler.h new file mode 100644 index 00000000000..a6620560673 --- /dev/null +++ b/lldb/tools/debugserver/source/FunctionProfiler.h @@ -0,0 +1,70 @@ +//===-- FunctionProfiler.h --------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 10/8/08. +// +//===----------------------------------------------------------------------===// + +#ifndef __FunctionProfiler_h__ +#define __FunctionProfiler_h__ + +// C Includes + +// C++ Includes +#include <map> +#include <vector> +#include <string> + +// Other libraries and framework includes + +// Project includes +#include "DNBDefs.h" +#include "DNBRuntimeAction.h" +#include "PThreadMutex.h" + +class DNBBreakpoint; +class MachProcess; + +class FunctionProfiler : public DNBRuntimeAction +{ +public: + FunctionProfiler (nub_addr_t start_addr, nub_addr_t stop_addr); + virtual ~FunctionProfiler (); + + //------------------------------------------------------------------ + // DNBRuntimeAction required functions + //------------------------------------------------------------------ + virtual void Initialize(nub_process_t pid); + virtual void ProcessStateChanged(nub_state_t state); + virtual void SharedLibraryStateChanged(DNBExecutableImageInfo *image_infos, nub_size_t num_image_infos) {} + + nub_bool_t BreakpointHit(nub_process_t pid, nub_thread_t tid, nub_break_t breakID); + bool ShouldStepProcess() const + { + return m_func_entered_count > 0; + } +protected: + static nub_bool_t BreakpointHitCallback (nub_process_t pid, nub_thread_t tid, nub_break_t breakID, void *baton); + void Clear(); + void SetBreakpoints(); + + nub_process_t m_pid; + nub_addr_t m_start_addr; + nub_addr_t m_stop_addr; + nub_break_t m_start_break_id; + nub_break_t m_stop_break_id; + uint32_t m_func_entered_count; + nub_addr_t m_last_pc; + uint32_t m_last_flags; + uint32_t m_consecutive_opcode_count; + uint32_t m_total_opcode_count; +}; + + +#endif // __FunctionProfiler_h__ diff --git a/lldb/tools/debugserver/source/MacOSX/CFBundle.cpp b/lldb/tools/debugserver/source/MacOSX/CFBundle.cpp new file mode 100644 index 00000000000..a15755044c3 --- /dev/null +++ b/lldb/tools/debugserver/source/MacOSX/CFBundle.cpp @@ -0,0 +1,87 @@ +//===-- CFBundle.cpp --------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 1/16/08. +// +//===----------------------------------------------------------------------===// + +#include "CFBundle.h" +#include "CFString.h" + +//---------------------------------------------------------------------- +// CFBundle constructor +//---------------------------------------------------------------------- +CFBundle::CFBundle(const char *path) : + CFReleaser<CFBundleRef>(), + m_bundle_url() +{ + if (path && path[0]) + SetPath(path); +} + +//---------------------------------------------------------------------- +// CFBundle copy constructor +//---------------------------------------------------------------------- +CFBundle::CFBundle(const CFBundle& rhs) : + CFReleaser<CFBundleRef>(rhs), + m_bundle_url(rhs.m_bundle_url) +{ + +} + +//---------------------------------------------------------------------- +// CFBundle copy constructor +//---------------------------------------------------------------------- +CFBundle& +CFBundle::operator=(const CFBundle& rhs) +{ + *this = rhs; + return *this; +} + +//---------------------------------------------------------------------- +// Destructor +//---------------------------------------------------------------------- +CFBundle::~CFBundle() +{ +} + +//---------------------------------------------------------------------- +// Set the path for a bundle by supplying a +//---------------------------------------------------------------------- +bool +CFBundle::SetPath (const char *path) +{ + CFAllocatorRef alloc = kCFAllocatorDefault; + // Release our old bundle and ULR + reset(); // This class is a CFReleaser<CFBundleRef> + m_bundle_url.reset(); + // Make a CFStringRef from the supplied path + CFString cf_path; + cf_path.SetFileSystemRepresentation(path); + if (cf_path.get()) + { + // Make our Bundle URL + m_bundle_url.reset (::CFURLCreateWithFileSystemPath (alloc, cf_path.get(), kCFURLPOSIXPathStyle, true)); + if (m_bundle_url.get()) + { + reset (::CFBundleCreate (alloc, m_bundle_url.get())); + } + } + return get() != NULL; +} + +CFStringRef +CFBundle::GetIdentifier () const +{ + CFBundleRef bundle = get(); + if (bundle != NULL) + return ::CFBundleGetIdentifier (bundle); + return NULL; +} diff --git a/lldb/tools/debugserver/source/MacOSX/CFBundle.h b/lldb/tools/debugserver/source/MacOSX/CFBundle.h new file mode 100644 index 00000000000..d980c0ba16f --- /dev/null +++ b/lldb/tools/debugserver/source/MacOSX/CFBundle.h @@ -0,0 +1,37 @@ +//===-- CFBundle.h ----------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 1/16/08. +// +//===----------------------------------------------------------------------===// + +#ifndef __CFBundle_h__ +#define __CFBundle_h__ + +#include "CFUtils.h" + +class CFBundle : public CFReleaser<CFBundleRef> +{ +public: + //------------------------------------------------------------------ + // Constructors and Destructors + //------------------------------------------------------------------ + CFBundle(const char *path = NULL); + CFBundle(const CFBundle& rhs); + CFBundle& operator=(const CFBundle& rhs); + virtual ~CFBundle(); + + bool SetPath (const char *path); + CFStringRef GetIdentifier () const; + +protected: + CFReleaser<CFURLRef> m_bundle_url; +}; + +#endif // #ifndef __CFBundle_h__ diff --git a/lldb/tools/debugserver/source/MacOSX/CFData.cpp b/lldb/tools/debugserver/source/MacOSX/CFData.cpp new file mode 100644 index 00000000000..94c93da544a --- /dev/null +++ b/lldb/tools/debugserver/source/MacOSX/CFData.cpp @@ -0,0 +1,85 @@ +//===-- CFData.cpp ----------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 1/16/08. +// +//===----------------------------------------------------------------------===// + +#include "CFData.h" + +//---------------------------------------------------------------------- +// CFData constructor +//---------------------------------------------------------------------- +CFData::CFData(CFDataRef data) : + CFReleaser<CFDataRef>(data) +{ + +} + +//---------------------------------------------------------------------- +// CFData copy constructor +//---------------------------------------------------------------------- +CFData::CFData(const CFData& rhs) : + CFReleaser<CFDataRef>(rhs) +{ + +} + +//---------------------------------------------------------------------- +// CFData copy constructor +//---------------------------------------------------------------------- +CFData& +CFData::operator=(const CFData& rhs) + +{ + *this = rhs; + return *this; +} + +//---------------------------------------------------------------------- +// Destructor +//---------------------------------------------------------------------- +CFData::~CFData() +{ +} + + +CFIndex +CFData::GetLength() const +{ + CFDataRef data = get(); + if (data) + return CFDataGetLength (data); + return 0; +} + + +const uint8_t* +CFData::GetBytePtr() const +{ + CFDataRef data = get(); + if (data) + return CFDataGetBytePtr (data); + return NULL; +} + +CFDataRef +CFData::Serialize(CFPropertyListRef plist, CFPropertyListFormat format) +{ + CFAllocatorRef alloc = kCFAllocatorDefault; + reset(); + CFReleaser<CFWriteStreamRef> stream (::CFWriteStreamCreateWithAllocatedBuffers (alloc, alloc)); + ::CFWriteStreamOpen (stream.get()); + CFIndex len = ::CFPropertyListWriteToStream (plist, stream.get(), format, NULL); + if (len > 0) + reset((CFDataRef)::CFWriteStreamCopyProperty (stream.get(), kCFStreamPropertyDataWritten)); + ::CFWriteStreamClose (stream.get()); + return get(); +} + diff --git a/lldb/tools/debugserver/source/MacOSX/CFData.h b/lldb/tools/debugserver/source/MacOSX/CFData.h new file mode 100644 index 00000000000..2c9d65d3af7 --- /dev/null +++ b/lldb/tools/debugserver/source/MacOSX/CFData.h @@ -0,0 +1,39 @@ +//===-- CFData.h ------------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 1/16/08. +// +//===----------------------------------------------------------------------===// + +#ifndef __CFData_h__ +#define __CFData_h__ + +#include "CFUtils.h" + +class CFData : public CFReleaser<CFDataRef> +{ +public: + //------------------------------------------------------------------ + // Constructors and Destructors + //------------------------------------------------------------------ + CFData(CFDataRef data = NULL); + CFData(const CFData& rhs); + CFData& operator=(const CFData& rhs); + virtual ~CFData(); + + CFDataRef Serialize(CFPropertyListRef plist, CFPropertyListFormat format); + const uint8_t* GetBytePtr () const; + CFIndex GetLength () const; +protected: + //------------------------------------------------------------------ + // Classes that inherit from CFData can see and modify these + //------------------------------------------------------------------ +}; + +#endif // #ifndef __CFData_h__ diff --git a/lldb/tools/debugserver/source/MacOSX/CFString.cpp b/lldb/tools/debugserver/source/MacOSX/CFString.cpp new file mode 100644 index 00000000000..819024ca3bc --- /dev/null +++ b/lldb/tools/debugserver/source/MacOSX/CFString.cpp @@ -0,0 +1,201 @@ +//===-- CFString.cpp --------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 1/16/08. +// +//===----------------------------------------------------------------------===// + +#include "CFString.h" +#include <string> +#include <glob.h> + +//---------------------------------------------------------------------- +// CFString constructor +//---------------------------------------------------------------------- +CFString::CFString(CFStringRef s) : + CFReleaser<CFStringRef> (s) +{ +} + +//---------------------------------------------------------------------- +// CFString copy constructor +//---------------------------------------------------------------------- +CFString::CFString(const CFString& rhs) : + CFReleaser<CFStringRef> (rhs) +{ + +} + +//---------------------------------------------------------------------- +// CFString copy constructor +//---------------------------------------------------------------------- +CFString& +CFString::operator=(const CFString& rhs) +{ + if (this != &rhs) + *this = rhs; + return *this; +} + +CFString::CFString (const char *cstr, CFStringEncoding cstr_encoding) : + CFReleaser<CFStringRef> () +{ + if (cstr && cstr[0]) + { + reset(::CFStringCreateWithCString(kCFAllocatorDefault, cstr, cstr_encoding)); + } +} + +//---------------------------------------------------------------------- +// Destructor +//---------------------------------------------------------------------- +CFString::~CFString() +{ +} + +const char * +CFString::GetFileSystemRepresentation(std::string& s) +{ + return CFString::FileSystemRepresentation(get(), s); +} + +CFStringRef +CFString::SetFileSystemRepresentation (const char *path) +{ + CFStringRef new_value = NULL; + if (path && path[0]) + new_value = ::CFStringCreateWithFileSystemRepresentation (kCFAllocatorDefault, path); + reset(new_value); + return get(); +} + + +CFStringRef +CFString::SetFileSystemRepresentationFromCFType (CFTypeRef cf_type) +{ + CFStringRef new_value = NULL; + if (cf_type != NULL) + { + CFTypeID cf_type_id = ::CFGetTypeID(cf_type); + + if (cf_type_id == ::CFStringGetTypeID()) + { + // Retain since we are using the existing object + new_value = (CFStringRef)::CFRetain(cf_type); + } + else if (cf_type_id == ::CFURLGetTypeID()) + { + new_value = ::CFURLCopyFileSystemPath((CFURLRef)cf_type, kCFURLPOSIXPathStyle); + } + } + reset(new_value); + return get(); +} + +CFStringRef +CFString::SetFileSystemRepresentationAndExpandTilde (const char *path) +{ + std::string expanded_path; + if (CFString::GlobPath(path, expanded_path)) + SetFileSystemRepresentation(expanded_path.c_str()); + else + reset(); + return get(); +} + +const char * +CFString::UTF8(std::string& str) +{ + return CFString::UTF8(get(), str); +} + +// Static function that puts a copy of the UTF8 contents of CF_STR into STR +// and returns the C string pointer that is contained in STR when successful, else +// NULL is returned. This allows the std::string parameter to own the extracted string, +// and also allows that string to be returned as a C string pointer that can be used. + +const char * +CFString::UTF8 (CFStringRef cf_str, std::string& str) +{ + if (cf_str) + { + const CFStringEncoding encoding = kCFStringEncodingUTF8; + CFIndex max_utf8_str_len = CFStringGetLength (cf_str); + max_utf8_str_len = CFStringGetMaximumSizeForEncoding (max_utf8_str_len, encoding); + if (max_utf8_str_len > 0) + { + str.resize(max_utf8_str_len); + if (!str.empty()) + { + if (CFStringGetCString (cf_str, &str[0], str.size(), encoding)) + { + str.resize(strlen(str.c_str())); + return str.c_str(); + } + } + } + } + return NULL; +} + +// Static function that puts a copy of the file system representation of CF_STR +// into STR and returns the C string pointer that is contained in STR when +// successful, else NULL is returned. This allows the std::string parameter +// to own the extracted string, and also allows that string to be returned as +// a C string pointer that can be used. + +const char * +CFString::FileSystemRepresentation (CFStringRef cf_str, std::string& str) +{ + if (cf_str) + { + CFIndex max_length = ::CFStringGetMaximumSizeOfFileSystemRepresentation (cf_str); + if (max_length > 0) + { + str.resize(max_length); + if (!str.empty()) + { + if (::CFStringGetFileSystemRepresentation (cf_str, &str[0], str.size())) + { + str.erase(::strlen(str.c_str())); + return str.c_str(); + } + } + } + } + str.erase(); + return NULL; +} + + +CFIndex +CFString::GetLength() const +{ + CFStringRef str = get(); + if (str) + return CFStringGetLength (str); + return 0; +} + + +const char* +CFString::GlobPath(const char* path, std::string &expanded_path) +{ + glob_t globbuf; + if (::glob (path, GLOB_TILDE, NULL, &globbuf) == 0) + { + expanded_path = globbuf.gl_pathv[0]; + ::globfree (&globbuf); + } + else + expanded_path.clear(); + + return expanded_path.c_str(); +} + diff --git a/lldb/tools/debugserver/source/MacOSX/CFString.h b/lldb/tools/debugserver/source/MacOSX/CFString.h new file mode 100644 index 00000000000..2b96b8237fc --- /dev/null +++ b/lldb/tools/debugserver/source/MacOSX/CFString.h @@ -0,0 +1,43 @@ +//===-- CFString.h ----------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 1/16/08. +// +//===----------------------------------------------------------------------===// + +#ifndef __CFString_h__ +#define __CFString_h__ + +#include "CFUtils.h" +#include <iosfwd> + +class CFString : public CFReleaser<CFStringRef> +{ +public: + //------------------------------------------------------------------ + // Constructors and Destructors + //------------------------------------------------------------------ + CFString (CFStringRef cf_str = NULL); + CFString (const char *s, CFStringEncoding encoding); + CFString (const CFString& rhs); + CFString& operator= (const CFString& rhs); + virtual ~CFString (); + + const char * GetFileSystemRepresentation (std::string& str); + CFStringRef SetFileSystemRepresentation (const char *path); + CFStringRef SetFileSystemRepresentationFromCFType (CFTypeRef cf_type); + CFStringRef SetFileSystemRepresentationAndExpandTilde (const char *path); + const char * UTF8 (std::string& str); + CFIndex GetLength() const; + static const char *UTF8 (CFStringRef cf_str, std::string& str); + static const char *FileSystemRepresentation (CFStringRef cf_str, std::string& str); + static const char* GlobPath(const char* path, std::string &expanded_path); +}; + +#endif // #ifndef __CFString_h__ diff --git a/lldb/tools/debugserver/source/MacOSX/CFUtils.h b/lldb/tools/debugserver/source/MacOSX/CFUtils.h new file mode 100644 index 00000000000..afa984fa11c --- /dev/null +++ b/lldb/tools/debugserver/source/MacOSX/CFUtils.h @@ -0,0 +1,81 @@ +//===-- CFUtils.h -----------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 3/5/07. +// +//===----------------------------------------------------------------------===// + +#ifndef __CFUtils_h__ +#define __CFUtils_h__ + +#include <CoreFoundation/CoreFoundation.h> + +#ifdef __cplusplus + +//---------------------------------------------------------------------- +// Templatized CF helper class that can own any CF pointer and will +// call CFRelease() on any valid pointer it owns unless that pointer is +// explicitly released using the release() member function. +//---------------------------------------------------------------------- +template <class T> +class CFReleaser +{ +public: + // Type names for the avlue + typedef T element_type; + + // Constructors and destructors + CFReleaser(T ptr = NULL) : _ptr(ptr) { } + CFReleaser(const CFReleaser& copy) : _ptr(copy.get()) + { + if (get()) + ::CFRetain(get()); + } + virtual ~CFReleaser() { reset(); } + + // Assignments + CFReleaser& operator= (const CFReleaser<T>& copy) + { + if (copy != *this) + { + // Replace our owned pointer with the new one + reset(copy.get()); + // Retain the current pointer that we own + if (get()) + ::CFRetain(get()); + } + } + // Get the address of the contained type + T * ptr_address() { return &_ptr; } + + // Access the pointer itself + const T get() const { return _ptr; } + T get() { return _ptr; } + + // Set a new value for the pointer and CFRelease our old + // value if we had a valid one. + void reset(T ptr = NULL) + { + if (ptr != _ptr) + { + if (_ptr != NULL) + ::CFRelease(_ptr); + _ptr = ptr; + } + } + + // Release ownership without calling CFRelease + T release() { T tmp = _ptr; _ptr = NULL; return tmp; } +private: + element_type _ptr; +}; + +#endif // #ifdef __cplusplus +#endif // #ifndef __CFUtils_h__ + diff --git a/lldb/tools/debugserver/source/MacOSX/MachDYLD.cpp b/lldb/tools/debugserver/source/MacOSX/MachDYLD.cpp new file mode 100644 index 00000000000..76288c55bf5 --- /dev/null +++ b/lldb/tools/debugserver/source/MacOSX/MachDYLD.cpp @@ -0,0 +1,679 @@ +//===-- MachDYLD.cpp --------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/29/07. +// +//===----------------------------------------------------------------------===// + +#include "MachDYLD.h" +#include "DNB.h" +#include "DNBDataRef.h" +#include <mach-o/loader.h> +#include "DNBLog.h" + +MachDYLD::MachDYLD() : + m_pid(INVALID_NUB_PROCESS), + m_addr_size(4), + m_dyld_addr(INVALID_NUB_ADDRESS), + m_dyld_all_image_infos_addr(INVALID_NUB_ADDRESS), + m_dylib_info_header(), + m_current_dylibs(), + m_changed_dylibs(), + m_notify_break_id(INVALID_NUB_BREAK_ID), + m_dyld_info_mutex(PTHREAD_MUTEX_RECURSIVE) +{ +} + +MachDYLD::~MachDYLD() +{ + Clear(); +} + + +void +MachDYLD::Clear() +{ + PThreadMutex::Locker locker(m_dyld_info_mutex); + + nub_process_t pid = m_pid; + if (pid != INVALID_NUB_PROCESS) + { + DNBProcessSetSharedLibraryInfoCallback ( pid, NULL, NULL); + DNBBreakpointClear(pid, m_notify_break_id); + } + + m_addr_size = 4; + m_dyld_addr = INVALID_NUB_ADDRESS; + m_dyld_all_image_infos_addr = INVALID_NUB_ADDRESS; + m_dylib_info_header.Clear(); + m_current_dylibs.clear(); + m_changed_dylibs.clear(); + m_notify_break_id = INVALID_NUB_BREAK_ID; +} + + +void +MachDYLD::Initialize(nub_process_t pid) +{ + //printf("MachDYLD::%s(0x%4.4x)\n", __FUNCTION__, pid); + Clear(); + m_pid = pid; +} + + +void +MachDYLD::ProcessStateChanged(nub_state_t state) +{ + //printf("MachDYLD::%s(%s)\n", __FUNCTION__, DNBStateAsString(state)); + + switch (state) + { + case eStateInvalid: + case eStateUnloaded: + case eStateExited: + case eStateDetached: + case eStateAttaching: + case eStateLaunching: + Clear(); + break; + + case eStateStopped: + // Keep trying find dyld each time we stop until we do + if (!FoundDYLD()) + { + assert(m_pid != INVALID_NUB_PROCESS); + DNBProcessSetSharedLibraryInfoCallback ( m_pid, CopySharedInfoCallback, this); + CheckForDYLDInMemory(); + } + break; + + case eStateRunning: + case eStateStepping: + case eStateCrashed: + case eStateSuspended: + break; + + default: + break; + } +} + +void +MachDYLD::SharedLibraryStateChanged(DNBExecutableImageInfo *image_infos, nub_size_t num_image_infos) +{ + //printf("MachDYLD::%s(%p, %u)\n", __FUNCTION__, image_infos, image_infos); + +} + +bool +MachDYLD::FoundDYLD() const +{ + return m_dyld_addr != INVALID_NUB_ADDRESS; +} + +bool +MachDYLD::CheckForDYLDInMemory() +{ +#if defined (__arm__) + return CheckForDYLDInMemory(0x2fe00000); +#else + return CheckForDYLDInMemory(0x8fe00000); +#endif +} + +bool +MachDYLD::CheckForDYLDInMemory(nub_addr_t addr) +{ + std::vector<uint8_t> dyld_header; + nub_size_t page_size = 0x1000; + dyld_header.resize(page_size); + nub_size_t bytes_read = DNBProcessMemoryRead(m_pid, addr, dyld_header.size(), &dyld_header[0]); + if (bytes_read > 0) + { + DNBDataRef::offset_t offset = 0; + DNBDataRef data(&dyld_header[0], bytes_read, false); + struct mach_header *header = (struct mach_header*)data.GetData(&offset, sizeof(struct mach_header)); + if (header) + { + switch (header->magic) + { + case MH_MAGIC: + case MH_CIGAM: + data.SetPointerSize(4); + m_addr_size = 4; + break; + + case MH_MAGIC_64: + case MH_CIGAM_64: + data.SetPointerSize(8); + m_addr_size = 8; + break; + + default: + return false; + } + + if (header->filetype == MH_DYLINKER) + { + // printf( "Found DYLD mach image at %8.8p", addr); + + m_dyld_all_image_infos_addr = DNBProcessLookupAddress(m_pid, "dyld_all_image_infos", "/usr/lib/dyld"); + +#if defined (__arm__) + m_dyld_all_image_infos_addr = 0x2fe3a004; +#endif + + if (m_dyld_all_image_infos_addr != INVALID_NUB_ADDRESS) + { + // printf( "Found DYLD data symbol 'dyld_all_image_infos' is %8.8p", m_dyld_all_image_infos_addr); + + if (ReadDYLIBInfo()) + { + if (m_dylib_info_header.notification != INVALID_NUB_ADDRESS) + { + m_notify_break_id = DNBBreakpointSet(m_pid, m_dylib_info_header.notification, 4, true); + if (NUB_BREAK_ID_IS_VALID(m_notify_break_id)) + { + DNBBreakpointSetCallback(m_pid, m_notify_break_id, MachDYLD::BreakpointHit, this); + m_dyld_addr = addr; + } + } + } + // if (DNBLogCheckLogBit(LOG_SHLIB)) + // Dump(DNBLogGetLogFile()); + } + return true; + } + } + } + return false; +} + +nub_bool_t +MachDYLD::BreakpointHit(nub_process_t pid, nub_thread_t tid, nub_break_t breakID, void *baton) +{ + MachDYLD *dyld = (MachDYLD*) baton; + //printf("MachDYLD::BreakpointHit called"); + dyld->ReadDYLIBInfo(); + DNBProcessSharedLibrariesUpdated(pid); + return false; // Don't stop the process, let it continue +} + +bool +MachDYLD::ReadDYLIBInfo() +{ + nub_addr_t addr = m_dyld_all_image_infos_addr; + if (addr != INVALID_NUB_ADDRESS) + { + PThreadMutex::Locker locker(m_dyld_info_mutex); + //printf("MachDYLD::ReadDYLIBInfo(addr =%8.8p)", addr); + bool swap = false; + uint32_t i = 0; + DYLIBInfo::collection previous_dylibs; + previous_dylibs.swap(m_current_dylibs); + uint8_t all_dylib_info_data[32]; + nub_size_t count = 8 + m_addr_size * 2; + nub_size_t bytes_read = DNBProcessMemoryRead(m_pid, addr, count, &all_dylib_info_data[0]); + if (bytes_read != count) + { + m_dylib_info_header.Clear(); + return false; + } + + DNBDataRef data(all_dylib_info_data, sizeof(all_dylib_info_data), swap); + data.SetPointerSize(m_addr_size); + DNBDataRef::offset_t offset = 0; + m_dylib_info_header.version = data.Get32(&offset); + m_dylib_info_header.dylib_info_count = data.Get32(&offset); + m_dylib_info_header.dylib_info_addr = data.GetPointer(&offset); + m_dylib_info_header.notification = data.GetPointer(&offset); + //printf( "%s: version=%d, count=%d, addr=%8.8p, notify=%8.8p", + // __PRETTY_FUNCTION__, + // m_dylib_info_header.version, + // m_dylib_info_header.dylib_info_count, + // m_dylib_info_header.dylib_info_addr, + // m_dylib_info_header.notification); + + switch (m_dylib_info_header.version) + { + case 1: // 10.4.x and prior + { + } + break; + + case 2: // 10.5 and later + { + } + break; + + default: + //printf( "Invalid dyld all_dylib_infos version number: %d", m_dylib_info_header.version); + return false; + break; + } + + // If we made it here, we are assuming that the all dylib info data should + // be valid, lets read the info array. + if (m_dylib_info_header.dylib_info_count > 0) + { + if (m_dylib_info_header.dylib_info_addr == 0) + { + //printf( "dyld is currently updating all_dylib_infos."); + } + else + { + m_current_dylibs.resize(m_dylib_info_header.dylib_info_count); + count = m_current_dylibs.size() * 3 * m_addr_size; + std::vector<uint8_t> info_data(count, 0); + bytes_read = DNBProcessMemoryRead(m_pid, m_dylib_info_header.dylib_info_addr, count, &info_data[0]); + if (bytes_read == count) + { + DNBDataRef::offset_t info_data_offset = 0; + DNBDataRef info_data_ref(&info_data[0], info_data.size(), swap); + info_data_ref.SetPointerSize(m_addr_size); + for (i = 0; info_data_ref.ValidOffset(info_data_offset); i++) + { + assert (i < m_current_dylibs.size()); + m_current_dylibs[i].address = info_data_ref.GetPointer(&info_data_offset); + nub_addr_t path_addr = info_data_ref.GetPointer(&info_data_offset); + m_current_dylibs[i].mod_date = info_data_ref.GetPointer(&info_data_offset); + + char raw_path[PATH_MAX]; + char resolved_path[PATH_MAX]; + bytes_read = DNBProcessMemoryRead(m_pid, path_addr, sizeof(raw_path), (char*)&raw_path[0]); + if (::realpath(raw_path, resolved_path)) + m_current_dylibs[i].path = resolved_path; + else + m_current_dylibs[i].path = raw_path; + } + assert(i == m_dylib_info_header.dylib_info_count); + + UpdateUUIDs(); + } + else + { + //printf( "unable to read all data for all_dylib_infos."); + m_current_dylibs.clear(); + return false; + } + } + } + // Read any UUID values that we can get + if (m_current_dylibs.empty()) + { + m_changed_dylibs = previous_dylibs; + const size_t num_changed_dylibs = m_changed_dylibs.size(); + for (i = 0; i < num_changed_dylibs; i++) + { + // Indicate the shared library was unloaded by giving it an invalid + // address... + m_changed_dylibs[i].address = INVALID_NUB_ADDRESS; + } + } + else + { + m_changed_dylibs.clear(); + + // Most of the time when we get shared library changes, they just + // get appended to the end of the list, so find out the min number + // of entries in the current and previous list that match and see + // how many are equal. + uint32_t curr_dylib_count = m_current_dylibs.size(); + uint32_t prev_dylib_count = previous_dylibs.size(); + uint32_t common_count = std::min<uint32_t>(prev_dylib_count, curr_dylib_count); + MachDYLD::DYLIBInfo::const_iterator curr_pos = m_current_dylibs.begin(); + MachDYLD::DYLIBInfo::const_iterator curr_end = m_current_dylibs.end(); + MachDYLD::DYLIBInfo::iterator prev_pos = previous_dylibs.begin(); + uint32_t idx; + for (idx = 0; idx < common_count; idx++) + { + if (*curr_pos == *prev_pos) + { + ++curr_pos; + ++prev_pos; + } + else + break; + } + + // Remove all the entries that were at the exact same index and that + // matched between the previous_dylibs and m_current_dylibs arrays. This will cover + // most of the cases as when shared libraries get loaded they get + // appended to the end of the list. + if (prev_pos != previous_dylibs.begin()) + { + previous_dylibs.erase(previous_dylibs.begin(), prev_pos); + } + + if (previous_dylibs.empty()) + { + // We only have new libraries to add, they are the only ones that + // have changed. + if (curr_pos != curr_end) + { + m_changed_dylibs.assign(curr_pos, curr_end); + } + } + else + { + // We still have items in our previous dylib list which means either + // one or more shared libraries got unloaded somewhere in the middle + // of the list, so we will manually search for each remaining item + // in our current list in the previous list + for (; curr_pos != curr_end; ++curr_pos) + { + MachDYLD::DYLIBInfo::iterator pos = std::find(previous_dylibs.begin(), previous_dylibs.end(), *curr_pos); + if (pos == previous_dylibs.end()) + { + // This dylib wasn't here before, add it to our change list + m_changed_dylibs.push_back(*curr_pos); + } + else + { + // This dylib was in our previous dylib list, it didn't + // change, so lets remove it from the previous list so we + // don't see it again. + previous_dylibs.erase(pos); + } + } + + // The only items left if our previous_dylibs array will be shared + // libraries that got unloaded (still in previous list, and not + // mentioned in the current list). + if (!previous_dylibs.empty()) + { + const size_t num_previous_dylibs = previous_dylibs.size(); + for (i = 0; i < num_previous_dylibs; i++) + { + // Indicate the shared library was unloaded by giving it + // an invalid address... + previous_dylibs[i].address = INVALID_NUB_ADDRESS; + } + // Add all remaining previous_dylibs to the changed list with + // invalidated addresses so we know they got unloaded. + m_changed_dylibs.insert(m_changed_dylibs.end(), previous_dylibs.begin(), previous_dylibs.end()); + } + } + } + return true; + } + return false; +} + + +void +MachDYLD::UpdateUUIDs() +{ + bool swap = false; + nub_size_t page_size = 0x1000; + uint32_t i; + // Read any UUID values that we can get + for (i = 0; i < m_dylib_info_header.dylib_info_count; i++) + { + if (!m_current_dylibs[i].UUIDValid()) + { + std::vector<uint8_t> bytes(page_size, 0); + nub_size_t bytes_read = DNBProcessMemoryRead(m_pid, m_current_dylibs[i].address, page_size, &bytes[0]); + if (bytes_read > 0) + { + DNBDataRef::offset_t offset = 0; + DNBDataRef data(&bytes[0], bytes_read, swap); + struct mach_header *header = (struct mach_header*)data.GetData(&offset, sizeof(struct mach_header)); + if (header) + { + switch (header->magic) + { + case MH_MAGIC: + case MH_CIGAM: + data.SetPointerSize(4); + m_addr_size = 4; + break; + + case MH_MAGIC_64: + case MH_CIGAM_64: + data.SetPointerSize(8); + m_addr_size = 8; + offset += 4; // Skip the extra reserved field in the 64 bit mach header + break; + + default: + continue; + } + + if (header->sizeofcmds > bytes_read) + { + bytes.resize(header->sizeofcmds); + nub_addr_t addr = m_current_dylibs[i].address + bytes_read; + bytes_read += DNBProcessMemoryRead(m_pid, addr , header->sizeofcmds - bytes_read, &bytes[bytes_read]); + } + assert(bytes_read >= header->sizeofcmds); + uint32_t cmd_idx; + DNBSegment segment; + + for (cmd_idx = 0; cmd_idx < header->ncmds; cmd_idx++) + { + if (data.ValidOffsetForDataOfSize(offset, sizeof(struct load_command))) + { + struct load_command load_cmd; + DNBDataRef::offset_t load_cmd_offset = offset; + load_cmd.cmd = data.Get32(&offset); + load_cmd.cmdsize = data.Get32(&offset); + switch (load_cmd.cmd) + { + case LC_SEGMENT: + { + strncpy(segment.name, data.GetCStr(&offset, 16), 16); + memset(&segment.name[16], 0, DNB_MAX_SEGMENT_NAME_LENGTH - 16); + segment.addr = data.Get32(&offset); + segment.size = data.Get32(&offset); + m_current_dylibs[i].segments.push_back(segment); + } + break; + + case LC_SEGMENT_64: + { + strncpy(segment.name, data.GetCStr(&offset, 16), 16); + memset(&segment.name[16], 0, DNB_MAX_SEGMENT_NAME_LENGTH - 16); + segment.addr = data.Get64(&offset); + segment.size = data.Get64(&offset); + m_current_dylibs[i].segments.push_back(segment); + } + break; + + case LC_UUID: + // We found our UUID, we can stop now... + memcpy(m_current_dylibs[i].uuid, data.GetData(&offset, 16), 16); + // if (DNBLogCheckLogBit(LOG_SHLIB)) + // { + // DNBLogThreaded("UUID found for aii[%d]:", i); + // m_current_dylibs[i].Dump(DNBLogGetLogFile()); + // } + break; + + default: + break; + } + // Set offset to be the beginning of the next load command. + offset = load_cmd_offset + load_cmd.cmdsize; + } + } + } + } + } + } +} + + +nub_addr_t +MachDYLD::GetSharedLibraryHeaderAddress(const char *shlib_path) const +{ + if (!m_current_dylibs.empty() && shlib_path && shlib_path[0]) + { + uint32_t i; + for (i = 0; i<m_current_dylibs.size(); i++) + { + if (m_current_dylibs[i].path == shlib_path) + return m_current_dylibs[i].address; + } + } + return INVALID_NUB_ADDRESS; +} + + +nub_size_t +MachDYLD::CopySharedLibraryInfo(DYLIBInfo::collection& dylib_coll, DNBExecutableImageInfo **image_infos) +{ + if (!dylib_coll.empty()) + { + size_t i; + size_t total_num_segments = 0; + size_t segment_index = 0; + for (i = 0; i<dylib_coll.size(); i++) + { + total_num_segments += dylib_coll[i].segments.size(); + } + size_t image_infos_byte_size = sizeof(DNBExecutableImageInfo) * dylib_coll.size(); + size_t all_segments_byte_size = sizeof(DNBSegment) * total_num_segments; + size_t total_byte_size = image_infos_byte_size + all_segments_byte_size; + + // Allocate enough space to fit all of the shared library information in + // a single buffer so consumers can free a single chunk of data when done + uint8_t *buf = (uint8_t*)malloc (total_byte_size); + + DNBExecutableImageInfo *info = (DNBExecutableImageInfo*)buf; + DNBSegment *all_segments = (DNBSegment*)(buf + image_infos_byte_size); + if (info) + { + for (i = 0; i<dylib_coll.size(); i++) + { + strncpy(info[i].name, dylib_coll[i].path.c_str(), PATH_MAX); + // NULL terminate paths that are too long (redundant for path + // that fit, but harmless + info[i].name[PATH_MAX-1] = '\0'; + info[i].header_addr = dylib_coll[i].address; + info[i].state = (dylib_coll[i].address == INVALID_NUB_ADDRESS ? eShlibStateUnloaded : eShlibStateLoaded); + memcpy(info[i].uuid, dylib_coll[i].uuid, sizeof(uuid_t)); + info[i].num_segments = dylib_coll[i].segments.size(); + if (info[i].num_segments == 0) + { + info[i].segments = NULL; + } + else + { + info[i].segments = &all_segments[segment_index]; + memcpy(info[i].segments, &(dylib_coll[i].segments[0]), sizeof(DNBSegment) * info[i].num_segments); + segment_index += info[i].num_segments; + } + + } + // Release ownership of the shared library array to the caller + *image_infos = info; + return dylib_coll.size(); + } + } + *image_infos = NULL; + return 0; +} + + + +nub_size_t +MachDYLD::CopySharedInfoCallback(nub_process_t pid, struct DNBExecutableImageInfo **image_infos, nub_bool_t only_changed, void *baton) +{ + MachDYLD *dyld = (MachDYLD*) baton; + + if (only_changed) + return dyld->CopyChangedShlibInfo(image_infos); + else + return dyld->CopyCurrentShlibInfo(image_infos); + + *image_infos = NULL; + return 0; +} + +nub_size_t +MachDYLD::CopyCurrentShlibInfo(DNBExecutableImageInfo **image_infos) +{ + PThreadMutex::Locker locker(m_dyld_info_mutex); + return CopySharedLibraryInfo(m_current_dylibs, image_infos); +} + + +nub_size_t +MachDYLD::CopyChangedShlibInfo(DNBExecutableImageInfo **image_infos) +{ + PThreadMutex::Locker locker(m_dyld_info_mutex); + return CopySharedLibraryInfo(m_changed_dylibs, image_infos); +} + + + +void +MachDYLD::DYLIBInfo::Dump(FILE *f) const +{ + if (f == NULL) + return; + if (address == INVALID_NUB_ADDRESS) + { + if (UUIDValid()) + { + fprintf(f, "UNLOADED %8.8llx %2.2X%2.2X%2.2X%2.2X-%2.2X%2.2X-%2.2X%2.2X-%2.2X%2.2X-%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X %s", + (uint64_t)mod_date, + uuid[ 0], uuid[ 1], uuid[ 2], uuid[ 3], + uuid[ 4], uuid[ 5], uuid[ 6], uuid[ 7], + uuid[ 8], uuid[ 9], uuid[10], uuid[11], + uuid[12], uuid[13], uuid[14], uuid[15], + path.c_str()); + } + else + { + fprintf(f, "UNLOADED %8.8llx %s", (uint64_t)mod_date, path.c_str()); + } + } + else + { + if (UUIDValid()) + { + fprintf(f, "%8.8llx %8.8llx %2.2X%2.2X%2.2X%2.2X-%2.2X%2.2X-%2.2X%2.2X-%2.2X%2.2X-%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X %s", + (uint64_t)address, + (uint64_t)mod_date, + uuid[ 0], uuid[ 1], uuid[ 2], uuid[ 3], + uuid[ 4], uuid[ 5], uuid[ 6], uuid[ 7], + uuid[ 8], uuid[ 9], uuid[10], uuid[11], + uuid[12], uuid[13], uuid[14], uuid[15], + path.c_str()); + } + else + { + fprintf(f, "%8.8llx %8.8llx %s", (uint64_t)address, (uint64_t)mod_date, path.c_str()); + } + } +} + +void +MachDYLD::Dump(FILE *f) const +{ + if (f == NULL) + return; + + PThreadMutex::Locker locker(m_dyld_info_mutex); + fprintf(f, "\n\tMachDYLD.m_dylib_info_header: version=%d, count=%d, addr=0x%llx, notify=0x%llx", + m_dylib_info_header.version, + m_dylib_info_header.dylib_info_count, + (uint64_t)m_dylib_info_header.dylib_info_addr, + (uint64_t)m_dylib_info_header.notification); + uint32_t i; + fprintf(f, "\n\tMachDYLD.m_current_dylibs"); + for (i = 0; i<m_current_dylibs.size(); i++) + m_current_dylibs[i].Dump(f); + fprintf(f, "\n\tMachDYLD.m_changed_dylibs"); + for (i = 0; i<m_changed_dylibs.size(); i++) + m_changed_dylibs[i].Dump(f); +} + diff --git a/lldb/tools/debugserver/source/MacOSX/MachDYLD.h b/lldb/tools/debugserver/source/MacOSX/MachDYLD.h new file mode 100644 index 00000000000..1c7747cd137 --- /dev/null +++ b/lldb/tools/debugserver/source/MacOSX/MachDYLD.h @@ -0,0 +1,145 @@ +//===-- MachDYLD.h ----------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/29/07. +// +//===----------------------------------------------------------------------===// + +#ifndef __MachDYLD_h__ +#define __MachDYLD_h__ + +#include "DNBDefs.h" +#include "DNBRuntimeAction.h" +#include "PThreadMutex.h" +#include <map> +#include <vector> +#include <string> + +class DNBBreakpoint; +class MachProcess; + +class MachDYLD : public DNBRuntimeAction +{ +public: + MachDYLD (); + virtual ~MachDYLD (); + + //------------------------------------------------------------------ + // DNBRuntimeAction required functions + //------------------------------------------------------------------ + virtual void Initialize(nub_process_t pid); + virtual void ProcessStateChanged(nub_state_t state); + virtual void SharedLibraryStateChanged(DNBExecutableImageInfo *image_infos, nub_size_t num_image_infos); + +protected: + bool CheckForDYLDInMemory(); + bool FoundDYLD() const; + void Clear(); + void Dump(FILE *f) const; + nub_process_t ProcessID() const { return m_pid; } + uint32_t AddrByteSize() const { return m_addr_size; } + nub_size_t CopyCurrentShlibInfo(DNBExecutableImageInfo **image_infos); + nub_size_t CopyChangedShlibInfo(DNBExecutableImageInfo **image_infos); + nub_addr_t GetSharedLibraryHeaderAddress(const char *shlib_path) const; + bool CheckForDYLDInMemory(nub_addr_t addr); + bool ReadDYLIBInfo (); + static nub_bool_t BreakpointHit (nub_process_t pid, nub_thread_t tid, nub_break_t breakID, void *baton); + void UpdateUUIDs(); + + struct DYLIBInfo + { + nub_addr_t address; // Address of mach header for this dylib + nub_addr_t mod_date; // Modification date for this dylib + std::string path; // Resolved path for this dylib + uint8_t uuid[16]; // UUID for this dylib if it has one, else all zeros + std::vector<DNBSegment> segments; // All segment vmaddr and vmsize pairs for this executable (from memory of inferior) + + DYLIBInfo() : + address(INVALID_NUB_ADDRESS), + mod_date(0), + path(), + segments() + { + memset(uuid, 0, 16); + } + + void Clear() + { + address = INVALID_NUB_ADDRESS; + mod_date = 0; + path.clear(); + segments.clear(); + memset(uuid, 0, 16); + } + + bool operator == (const DYLIBInfo& rhs) const + { + return address == rhs.address + && mod_date == rhs.mod_date + && path == rhs.path + && memcmp(uuid, rhs.uuid, 16) == 0; + } + bool UUIDValid() const + { + return uuid[ 0] || uuid[ 1] || uuid[ 2] || uuid[ 3] || + uuid[ 4] || uuid[ 5] || uuid[ 6] || uuid[ 7] || + uuid[ 8] || uuid[ 9] || uuid[10] || uuid[11] || + uuid[12] || uuid[13] || uuid[14] || uuid[15]; + } + + void Dump(FILE *f) const; + typedef std::vector<DYLIBInfo> collection; + typedef collection::iterator iterator; + typedef collection::const_iterator const_iterator; + }; + struct InfoHeader + { + uint32_t version; /* == 1 in Mac OS X 10.4, == 2 in Mac OS 10.5 */ + uint32_t dylib_info_count; + nub_addr_t dylib_info_addr; + nub_addr_t notification; + bool processDetachedFromSharedRegion; + + InfoHeader() : + version(0), + dylib_info_count(0), + dylib_info_addr(INVALID_NUB_ADDRESS), + notification(INVALID_NUB_ADDRESS), + processDetachedFromSharedRegion(false) + { + } + + void Clear() + { + version = 0; + dylib_info_count = 0; + dylib_info_addr = INVALID_NUB_ADDRESS; + notification = INVALID_NUB_ADDRESS; + processDetachedFromSharedRegion = false; + } + + bool IsValid() const + { + return version == 1 || version == 2; + } + }; + static nub_size_t CopySharedLibraryInfo(DYLIBInfo::collection& dylib_coll, DNBExecutableImageInfo **image_infos); + static nub_size_t CopySharedInfoCallback(nub_process_t pid, struct DNBExecutableImageInfo **image_infos, nub_bool_t only_changed, void *baton); + nub_process_t m_pid; + uint32_t m_addr_size; + nub_addr_t m_dyld_addr; + nub_addr_t m_dyld_all_image_infos_addr; + InfoHeader m_dylib_info_header; + DYLIBInfo::collection m_current_dylibs; // Current shared libraries information + DYLIBInfo::collection m_changed_dylibs; // Shared libraries that changed since last shared library update + nub_break_t m_notify_break_id; + mutable PThreadMutex m_dyld_info_mutex; +}; + +#endif // #ifndef __MachDYLD_h__ diff --git a/lldb/tools/debugserver/source/MacOSX/MachException.cpp b/lldb/tools/debugserver/source/MacOSX/MachException.cpp new file mode 100644 index 00000000000..356160bb621 --- /dev/null +++ b/lldb/tools/debugserver/source/MacOSX/MachException.cpp @@ -0,0 +1,533 @@ +//===-- MachException.cpp ---------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/18/07. +// +//===----------------------------------------------------------------------===// + +#include "MachException.h" +#include "MachProcess.h" +#include "DNB.h" +#include "DNBError.h" +#include <sys/types.h> +#include "DNBLog.h" +#include "PThreadMutex.h" +#include "SysSignal.h" +#include <errno.h> +#include <sys/ptrace.h> + +// Routine mach_exception_raise +extern "C" +kern_return_t catch_mach_exception_raise +( + mach_port_t exception_port, + mach_port_t thread, + mach_port_t task, + exception_type_t exception, + mach_exception_data_t code, + mach_msg_type_number_t codeCnt +); + +extern "C" +kern_return_t catch_mach_exception_raise_state +( + mach_port_t exception_port, + exception_type_t exception, + const mach_exception_data_t code, + mach_msg_type_number_t codeCnt, + int *flavor, + const thread_state_t old_state, + mach_msg_type_number_t old_stateCnt, + thread_state_t new_state, + mach_msg_type_number_t *new_stateCnt +); + +// Routine mach_exception_raise_state_identity +extern "C" +kern_return_t catch_mach_exception_raise_state_identity +( + mach_port_t exception_port, + mach_port_t thread, + mach_port_t task, + exception_type_t exception, + mach_exception_data_t code, + mach_msg_type_number_t codeCnt, + int *flavor, + thread_state_t old_state, + mach_msg_type_number_t old_stateCnt, + thread_state_t new_state, + mach_msg_type_number_t *new_stateCnt +); + +extern "C" boolean_t mach_exc_server( + mach_msg_header_t *InHeadP, + mach_msg_header_t *OutHeadP); + +// Any access to the g_message variable should be done by locking the +// g_message_mutex first, using the g_message variable, then unlocking +// the g_message_mutex. See MachException::Message::CatchExceptionRaise() +// for sample code. + +static MachException::Data *g_message = NULL; +//static pthread_mutex_t g_message_mutex = PTHREAD_MUTEX_INITIALIZER; + + +extern "C" +kern_return_t +catch_mach_exception_raise_state +( + mach_port_t exc_port, + exception_type_t exc_type, + const mach_exception_data_t exc_data, + mach_msg_type_number_t exc_data_count, + int * flavor, + const thread_state_t old_state, + mach_msg_type_number_t old_stateCnt, + thread_state_t new_state, + mach_msg_type_number_t * new_stateCnt +) +{ + if (DNBLogCheckLogBit(LOG_EXCEPTIONS)) + { + DNBLogThreaded("::%s ( exc_port = 0x%4.4x, exc_type = %d ( %s ), exc_data = " MACH_EXCEPTION_DATA_FMT_HEX ", exc_data_count = %d)", + __FUNCTION__, + exc_port, + exc_type, MachException::Name(exc_type), + exc_data, + exc_data_count); + } + return KERN_FAILURE; +} + +extern "C" +kern_return_t +catch_mach_exception_raise_state_identity +( + mach_port_t exc_port, + mach_port_t thread_port, + mach_port_t task_port, + exception_type_t exc_type, + mach_exception_data_t exc_data, + mach_msg_type_number_t exc_data_count, + int * flavor, + thread_state_t old_state, + mach_msg_type_number_t old_stateCnt, + thread_state_t new_state, + mach_msg_type_number_t *new_stateCnt +) +{ + kern_return_t kret; + if (DNBLogCheckLogBit(LOG_EXCEPTIONS)) + { + DNBLogThreaded("::%s ( exc_port = 0x%4.4x, thd_port = 0x%4.4x, tsk_port = 0x%4.4x, exc_type = %d ( %s ), exc_data[%d] = { " MACH_EXCEPTION_DATA_FMT_HEX ", " MACH_EXCEPTION_DATA_FMT_HEX " })", + __FUNCTION__, + exc_port, + thread_port, + task_port, + exc_type, MachException::Name(exc_type), + exc_data_count, + exc_data_count > 0 ? exc_data[0] : 0xBADDBADD, + exc_data_count > 1 ? exc_data[1] : 0xBADDBADD); + } + kret = mach_port_deallocate (mach_task_self (), task_port); + kret = mach_port_deallocate (mach_task_self (), thread_port); + + return KERN_FAILURE; +} + +extern "C" +kern_return_t +catch_mach_exception_raise +( + mach_port_t exc_port, + mach_port_t thread_port, + mach_port_t task_port, + exception_type_t exc_type, + mach_exception_data_t exc_data, + mach_msg_type_number_t exc_data_count) +{ + if (DNBLogCheckLogBit(LOG_EXCEPTIONS)) + { + DNBLogThreaded("::%s ( exc_port = 0x%4.4x, thd_port = 0x%4.4x, tsk_port = 0x%4.4x, exc_type = %d ( %s ), exc_data[%d] = { " MACH_EXCEPTION_DATA_FMT_HEX ", " MACH_EXCEPTION_DATA_FMT_HEX " })", + __FUNCTION__, + exc_port, + thread_port, + task_port, + exc_type, MachException::Name(exc_type), + exc_data_count, + exc_data_count > 0 ? exc_data[0] : 0xBADDBADD, + exc_data_count > 1 ? exc_data[1] : 0xBADDBADD); + } + + g_message->task_port = task_port; + g_message->thread_port = thread_port; + g_message->exc_type = exc_type; + g_message->exc_data.resize(exc_data_count); + ::memcpy (&g_message->exc_data[0], exc_data, g_message->exc_data.size() * sizeof (mach_exception_data_type_t)); + return KERN_SUCCESS; +} + + +void +MachException::Message::Dump() const +{ + DNBLogThreadedIf(LOG_EXCEPTIONS, + " exc_msg { bits = 0x%8.8lx size = 0x%8.8lx remote-port = 0x%8.8lx local-port = 0x%8.8lx reserved = 0x%8.8lx id = 0x%8.8lx } ", + exc_msg.hdr.msgh_bits, + exc_msg.hdr.msgh_size, + exc_msg.hdr.msgh_remote_port, + exc_msg.hdr.msgh_local_port, + exc_msg.hdr.msgh_reserved, + exc_msg.hdr.msgh_id); + + DNBLogThreadedIf(LOG_EXCEPTIONS, + "reply_msg { bits = 0x%8.8lx size = 0x%8.8lx remote-port = 0x%8.8lx local-port = 0x%8.8lx reserved = 0x%8.8lx id = 0x%8.8lx }", + reply_msg.hdr.msgh_bits, + reply_msg.hdr.msgh_size, + reply_msg.hdr.msgh_remote_port, + reply_msg.hdr.msgh_local_port, + reply_msg.hdr.msgh_reserved, + reply_msg.hdr.msgh_id); + + state.Dump(); +} + +bool +MachException::Data::GetStopInfo(struct DNBThreadStopInfo *stop_info) const +{ + // Zero out the structure. + memset(stop_info, 0, sizeof(struct DNBThreadStopInfo)); + // We always stop with a mach exceptions + stop_info->reason = eStopTypeException; + // Save the EXC_XXXX exception type + stop_info->details.exception.type = exc_type; + + // Fill in a text description + const char * exc_name = MachException::Name(exc_type); + char *desc = stop_info->description; + const char *end_desc = desc + DNB_THREAD_STOP_INFO_MAX_DESC_LENGTH; + if (exc_name) + desc += snprintf(desc, DNB_THREAD_STOP_INFO_MAX_DESC_LENGTH, "%s", exc_name); + else + desc += snprintf(desc, DNB_THREAD_STOP_INFO_MAX_DESC_LENGTH, "%i", exc_type); + + stop_info->details.exception.data_count = exc_data.size(); + + int soft_signal = SoftSignal(); + if (soft_signal) + { + if (desc < end_desc) + { + const char *sig_str = SysSignal::Name(soft_signal); + desc += snprintf(desc, end_desc - desc, " EXC_SOFT_SIGNAL( %i ( %s ))", soft_signal, sig_str ? sig_str : "unknown signal"); + } + } + else + { + // No special disassembly for exception data, just + size_t idx; + if (desc < end_desc) + { + desc += snprintf(desc, end_desc - desc, " data[%zu] = {", stop_info->details.exception.data_count); + + for (idx = 0; desc < end_desc && idx < stop_info->details.exception.data_count; ++idx) + desc += snprintf(desc, end_desc - desc, MACH_EXCEPTION_DATA_FMT_MINHEX "%c", exc_data[idx], ((idx + 1 == stop_info->details.exception.data_count) ? '}' : ',')); + } + } + + // Copy the exception data + size_t i; + for (i=0; i<stop_info->details.exception.data_count; i++) + stop_info->details.exception.data[i] = exc_data[i]; + + return true; +} + + +void +MachException::Data::DumpStopReason() const +{ + int soft_signal = SoftSignal(); + if (soft_signal) + { + const char *signal_str = SysSignal::Name(soft_signal); + if (signal_str) + DNBLog("signal(%s)", signal_str); + else + DNBLog("signal(%i)", soft_signal); + return; + } + DNBLog("%s", Name(exc_type)); +} + +kern_return_t +MachException::Message::Receive(mach_port_t port, mach_msg_option_t options, mach_msg_timeout_t timeout, mach_port_t notify_port) +{ + DNBError err; + const bool log_exceptions = DNBLogCheckLogBit(LOG_EXCEPTIONS); + mach_msg_timeout_t mach_msg_timeout = options & MACH_RCV_TIMEOUT ? timeout : 0; + if (log_exceptions && ((options & MACH_RCV_TIMEOUT) == 0)) + { + // Dump this log message if we have no timeout in case it never returns + DNBLogThreaded("::mach_msg ( msg->{bits = %#x, size = %u remote_port = %#x, local_port = %#x, reserved = 0x%x, id = 0x%x}, option = %#x, send_size = %u, rcv_size = %u, rcv_name = %#x, timeout = %u, notify = %#x)", + exc_msg.hdr.msgh_bits, + exc_msg.hdr.msgh_size, + exc_msg.hdr.msgh_remote_port, + exc_msg.hdr.msgh_local_port, + exc_msg.hdr.msgh_reserved, + exc_msg.hdr.msgh_id, + options, + 0, + sizeof (exc_msg.data), + port, + mach_msg_timeout, + notify_port); + } + + err = ::mach_msg (&exc_msg.hdr, + options, // options + 0, // Send size + sizeof (exc_msg.data), // Receive size + port, // exception port to watch for exception on + mach_msg_timeout, // timeout in msec (obeyed only if MACH_RCV_TIMEOUT is ORed into the options parameter) + notify_port); + + // Dump any errors we get + if (log_exceptions) + { + err.LogThreaded("::mach_msg ( msg->{bits = %#x, size = %u remote_port = %#x, local_port = %#x, reserved = 0x%x, id = 0x%x}, option = %#x, send_size = %u, rcv_size = %u, rcv_name = %#x, timeout = %u, notify = %#x)", + exc_msg.hdr.msgh_bits, + exc_msg.hdr.msgh_size, + exc_msg.hdr.msgh_remote_port, + exc_msg.hdr.msgh_local_port, + exc_msg.hdr.msgh_reserved, + exc_msg.hdr.msgh_id, + options, + 0, + sizeof (exc_msg.data), + port, + mach_msg_timeout, + notify_port); + } + return err.Error(); +} + +bool +MachException::Message::CatchExceptionRaise() +{ + bool success = false; + // locker will keep a mutex locked until it goes out of scope +// PThreadMutex::Locker locker(&g_message_mutex); + // DNBLogThreaded("calling mach_exc_server"); + g_message = &state; + // The exc_server function is the MIG generated server handling function + // to handle messages from the kernel relating to the occurrence of an + // exception in a thread. Such messages are delivered to the exception port + // set via thread_set_exception_ports or task_set_exception_ports. When an + // exception occurs in a thread, the thread sends an exception message to + // its exception port, blocking in the kernel waiting for the receipt of a + // reply. The exc_server function performs all necessary argument handling + // for this kernel message and calls catch_exception_raise, + // catch_exception_raise_state or catch_exception_raise_state_identity, + // which should handle the exception. If the called routine returns + // KERN_SUCCESS, a reply message will be sent, allowing the thread to + // continue from the point of the exception; otherwise, no reply message + // is sent and the called routine must have dealt with the exception + // thread directly. + if (mach_exc_server (&exc_msg.hdr, &reply_msg.hdr)) + { + success = true; + } + else if (DNBLogCheckLogBit(LOG_EXCEPTIONS)) + { + DNBLogThreaded("mach_exc_server returned zero..."); + } + g_message = NULL; + return success; +} + + + +kern_return_t +MachException::Message::Reply(MachProcess *process, int signal) +{ + // Reply to the exception... + DNBError err; + + // If we had a soft signal, we need to update the thread first so it can + // continue without signaling + int soft_signal = state.SoftSignal(); + if (soft_signal) + { + int state_pid = -1; + if (process->Task().TaskPort() == state.task_port) + { + // This is our task, so we can update the signal to send to it + state_pid = process->ProcessID(); + soft_signal = signal; + } + else + { + err = ::pid_for_task(state.task_port, &state_pid); + } + + assert (state_pid != -1); + if (state_pid != -1) + { + errno = 0; + if (::ptrace (PT_THUPDATE, state_pid, (caddr_t)state.thread_port, soft_signal) != 0) + err.SetError(errno, DNBError::POSIX); + else + err.Clear(); + + if (DNBLogCheckLogBit(LOG_EXCEPTIONS) || err.Fail()) + err.LogThreaded("::ptrace (request = PT_THUPDATE, pid = 0x%4.4x, tid = 0x%4.4x, signal = %i)", state_pid, state.thread_port, soft_signal); + } + } + + DNBLogThreadedIf(LOG_EXCEPTIONS, "::mach_msg ( msg->{bits = %#x, size = %u, remote_port = %#x, local_port = %#x, reserved = 0x%x, id = 0x%x}, option = %#x, send_size = %u, rcv_size = %u, rcv_name = %#x, timeout = %u, notify = %#x)", + reply_msg.hdr.msgh_bits, + reply_msg.hdr.msgh_size, + reply_msg.hdr.msgh_remote_port, + reply_msg.hdr.msgh_local_port, + reply_msg.hdr.msgh_reserved, + reply_msg.hdr.msgh_id, + MACH_SEND_MSG | MACH_SEND_INTERRUPT, + reply_msg.hdr.msgh_size, + 0, + MACH_PORT_NULL, + MACH_MSG_TIMEOUT_NONE, + MACH_PORT_NULL); + + err = ::mach_msg ( &reply_msg.hdr, + MACH_SEND_MSG | MACH_SEND_INTERRUPT, + reply_msg.hdr.msgh_size, + 0, + MACH_PORT_NULL, + MACH_MSG_TIMEOUT_NONE, + MACH_PORT_NULL); + + if (err.Fail()) + { + if (err.Error() == MACH_SEND_INTERRUPTED) + { + if (DNBLogCheckLogBit(LOG_EXCEPTIONS)) + err.LogThreaded("::mach_msg() - send interrupted"); + // TODO: keep retrying to reply??? + } + else + { + if (state.task_port == process->Task().TaskPort()) + { + if (DNBLogCheckLogBit(LOG_EXCEPTIONS)) + err.LogThreaded("::mach_msg() - failed (task)"); + abort (); + } + else + { + if (DNBLogCheckLogBit(LOG_EXCEPTIONS)) + err.LogThreaded("::mach_msg() - failed (child of task)"); + } + } + } + + return err.Error(); +} + + +void +MachException::Data::Dump() const +{ + const char *exc_type_name = MachException::Name(exc_type); + DNBLogThreadedIf(LOG_EXCEPTIONS, " state { task_port = 0x%4.4x, thread_port = 0x%4.4x, exc_type = %i (%s) ...", task_port, thread_port, exc_type, exc_type_name ? exc_type_name : "???"); + + const size_t exc_data_count = exc_data.size(); + // Dump any special exception data contents + int soft_signal = SoftSignal(); + if (soft_signal != 0) + { + const char *sig_str = SysSignal::Name(soft_signal); + DNBLogThreadedIf(LOG_EXCEPTIONS, " exc_data: EXC_SOFT_SIGNAL (%i (%s))", soft_signal, sig_str ? sig_str : "unknown signal"); + } + else + { + // No special disassembly for this data, just dump the data + size_t idx; + for (idx = 0; idx < exc_data_count; ++idx) + { + DNBLogThreadedIf(LOG_EXCEPTIONS, " exc_data[%u]: " MACH_EXCEPTION_DATA_FMT_HEX, idx, exc_data[idx]); + } + } +} + + +kern_return_t +MachException::PortInfo::Save (task_t task) +{ + count = (sizeof (ports) / sizeof (ports[0])); + DNBLogThreadedIf(LOG_EXCEPTIONS | LOG_VERBOSE, "MachException::PortInfo::Save ( task = 0x%4.4x )", task); + DNBError err; + err = ::task_get_exception_ports (task, EXC_MASK_ALL, masks, &count, ports, behaviors, flavors); + if (DNBLogCheckLogBit(LOG_EXCEPTIONS) || err.Fail()) + err.LogThreaded("::task_get_exception_ports ( task = 0x%4.4x, mask = 0x%x, maskCnt => %u, ports, behaviors, flavors )", task, EXC_MASK_ALL, count); + if (err.Fail()) + count = 0; + return err.Error(); +} + +kern_return_t +MachException::PortInfo::Restore (task_t task) +{ + DNBLogThreadedIf(LOG_EXCEPTIONS | LOG_VERBOSE, "MachException::PortInfo::Restore( task = 0x%4.4x )", task); + uint32_t i = 0; + DNBError err; + if (count > 0) + { + for (i = 0; i < count; i++) + { + err = ::task_set_exception_ports (task, masks[i], ports[i], behaviors[i], flavors[i]); + if (DNBLogCheckLogBit(LOG_EXCEPTIONS) || err.Fail()) + { + err.LogThreaded("::task_set_exception_ports ( task = 0x%4.4x, exception_mask = 0x%8.8x, new_port = 0x%4.4x, behavior = 0x%8.8x, new_flavor = 0x%8.8x )", task, masks[i], ports[i], behaviors[i], flavors[i]); + // Bail if we encounter any errors + } + + if (err.Fail()) + break; + } + } + count = 0; + return err.Error(); +} + +const char * +MachException::Name(exception_type_t exc_type) +{ + switch (exc_type) + { + case EXC_BAD_ACCESS: return "EXC_BAD_ACCESS"; + case EXC_BAD_INSTRUCTION: return "EXC_BAD_INSTRUCTION"; + case EXC_ARITHMETIC: return "EXC_ARITHMETIC"; + case EXC_EMULATION: return "EXC_EMULATION"; + case EXC_SOFTWARE: return "EXC_SOFTWARE"; + case EXC_BREAKPOINT: return "EXC_BREAKPOINT"; + case EXC_SYSCALL: return "EXC_SYSCALL"; + case EXC_MACH_SYSCALL: return "EXC_MACH_SYSCALL"; + case EXC_RPC_ALERT: return "EXC_RPC_ALERT"; +#ifdef EXC_CRASH + case EXC_CRASH: return "EXC_CRASH"; +#endif + default: + break; + } + return NULL; +} + + + diff --git a/lldb/tools/debugserver/source/MacOSX/MachException.h b/lldb/tools/debugserver/source/MacOSX/MachException.h new file mode 100644 index 00000000000..5dc394bd55a --- /dev/null +++ b/lldb/tools/debugserver/source/MacOSX/MachException.h @@ -0,0 +1,147 @@ +//===-- MachException.h -----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/18/07. +// +//===----------------------------------------------------------------------===// + + +#ifndef __MachException_h__ +#define __MachException_h__ + +#include <mach/mach.h> +#include <vector> +#include "DNBConfig.h" + +#ifdef HAVE_64_BIT_MACH_EXCEPTIONS + +#define MACH_EXCEPTION_DATA_FMT_DEC "%lld" +#define MACH_EXCEPTION_DATA_FMT_HEX "0x%16.16llx" +#define MACH_EXCEPTION_DATA_FMT_MINHEX "0x%llx" + +#else + +#define MACH_EXCEPTION_DATA_FMT_DEC "%d" +#define MACH_EXCEPTION_DATA_FMT_HEX "0x%8.8x" +#define MACH_EXCEPTION_DATA_FMT_MINHEX "0x%x" + +#endif + +class MachProcess; +class PThreadMutex; + +typedef union MachMessageTag +{ + mach_msg_header_t hdr; + char data[1024]; +} MachMessage; + + +class MachException +{ +public: + + struct PortInfo + { + exception_mask_t masks[EXC_TYPES_COUNT]; + mach_port_t ports[EXC_TYPES_COUNT]; + exception_behavior_t behaviors[EXC_TYPES_COUNT]; + thread_state_flavor_t flavors[EXC_TYPES_COUNT]; + mach_msg_type_number_t count; + + kern_return_t Save(task_t task); + kern_return_t Restore(task_t task); + }; + + struct Data + { + task_t task_port; + thread_t thread_port; + exception_type_t exc_type; + std::vector<mach_exception_data_type_t> exc_data; + Data() : + task_port(TASK_NULL), + thread_port(THREAD_NULL), + exc_type(0), + exc_data() + { + } + + void Clear() + { + task_port = TASK_NULL; + thread_port = THREAD_NULL; + exc_type = 0; + exc_data.clear(); + } + bool IsValid() const + { + return task_port != TASK_NULL && + thread_port != THREAD_NULL && + exc_type != 0; + } + // Return the SoftSignal for this MachException data, or zero if there is none + int SoftSignal() const + { + if (exc_type == EXC_SOFTWARE && exc_data.size() == 2 && exc_data[0] == EXC_SOFT_SIGNAL) + return exc_data[1]; + return 0; + } + bool IsBreakpoint() const + { + return (exc_type == EXC_BREAKPOINT) || ((exc_type == EXC_SOFTWARE) && exc_data[0] == 1); + } + void Dump() const; + void DumpStopReason() const; + bool GetStopInfo(struct DNBThreadStopInfo *stop_info) const; + }; + + struct Message + { + MachMessage exc_msg; + MachMessage reply_msg; + Data state; + + Message() : + state() + { + memset(&exc_msg, 0, sizeof(exc_msg)); + memset(&reply_msg, 0, sizeof(reply_msg)); + } + bool CatchExceptionRaise(); + void Dump() const; + kern_return_t Reply (MachProcess *process, int signal); + kern_return_t Receive( mach_port_t receive_port, + mach_msg_option_t options, + mach_msg_timeout_t timeout, + mach_port_t notify_port = MACH_PORT_NULL); + + typedef std::vector<Message> collection; + typedef collection::iterator iterator; + typedef collection::const_iterator const_iterator; + }; + + enum + { + e_actionForward, // Forward signal to inferior process + e_actionStop, // Stop when this signal is received + }; + struct Action + { + task_t task_port; // Set to TASK_NULL for any TASK + thread_t thread_port; // Set to THREAD_NULL for any thread + exception_type_t exc_mask; // Mach exception mask to watch for + std::vector<mach_exception_data_type_t> exc_data_mask; // Mask to apply to exception data, or empty to ignore exc_data value for exception + std::vector<mach_exception_data_type_t> exc_data_value; // Value to compare to exception data after masking, or empty to ignore exc_data value for exception + uint8_t flags; // Action flags describing what to do with the exception + }; + static const char *Name(exception_type_t exc_type); +}; + +#endif diff --git a/lldb/tools/debugserver/source/MacOSX/MachProcess.cpp b/lldb/tools/debugserver/source/MacOSX/MachProcess.cpp new file mode 100644 index 00000000000..2bfc7603e2a --- /dev/null +++ b/lldb/tools/debugserver/source/MacOSX/MachProcess.cpp @@ -0,0 +1,2008 @@ +//===-- MachProcess.cpp -----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/15/07. +// +//===----------------------------------------------------------------------===// + +#include "DNB.h" +#include <mach/mach.h> +#include <spawn.h> +#include <sys/fcntl.h> +#include <sys/types.h> +#include <sys/ptrace.h> +#include <sys/stat.h> +#include <unistd.h> +#include "MacOSX/CFUtils.h" +#include "SysSignal.h" + +#include <algorithm> +#include <map> + +#include "DNBDataRef.h" +#include "DNBLog.h" +#include "DNBThreadResumeActions.h" +#include "DNBTimer.h" +#include "MachProcess.h" +#include "PseudoTerminal.h" + +#include "CFBundle.h" +#include "CFData.h" +#include "CFString.h" + +static CFStringRef CopyBundleIDForPath (const char *app_buncle_path, DNBError &err_str); + +#if defined (__arm__) + +#include <CoreFoundation/CoreFoundation.h> +#include <SpringBoardServices/SpringBoardServer.h> +#include <SpringBoardServices/SBSWatchdogAssertion.h> + + +static bool +IsSBProcess (nub_process_t pid) +{ + bool opt_runningApps = true; + bool opt_debuggable = false; + + CFReleaser<CFArrayRef> sbsAppIDs (::SBSCopyApplicationDisplayIdentifiers (opt_runningApps, opt_debuggable)); + if (sbsAppIDs.get() != NULL) + { + CFIndex count = ::CFArrayGetCount (sbsAppIDs.get()); + CFIndex i = 0; + for (i = 0; i < count; i++) + { + CFStringRef displayIdentifier = (CFStringRef)::CFArrayGetValueAtIndex (sbsAppIDs.get(), i); + + // Get the process id for the app (if there is one) + pid_t sbs_pid = INVALID_NUB_PROCESS; + if (::SBSProcessIDForDisplayIdentifier ((CFStringRef)displayIdentifier, &sbs_pid) == TRUE) + { + if (sbs_pid == pid) + return true; + } + } + } + return false; +} + + +#endif + +#if 0 +#define DEBUG_LOG(fmt, ...) printf(fmt, ## __VA_ARGS__) +#else +#define DEBUG_LOG(fmt, ...) +#endif + +#ifndef MACH_PROCESS_USE_POSIX_SPAWN +#define MACH_PROCESS_USE_POSIX_SPAWN 1 +#endif + + +MachProcess::MachProcess() : + m_pid (0), + m_child_stdin (-1), + m_child_stdout (-1), + m_child_stderr (-1), + m_path (), + m_args (), + m_task (this), + m_flags (eMachProcessFlagsNone), + m_stdio_thread (0), + m_stdio_mutex (PTHREAD_MUTEX_RECURSIVE), + m_stdout_data (), + m_threadList (), + m_exception_messages (), + m_exception_messages_mutex (PTHREAD_MUTEX_RECURSIVE), + m_err (KERN_SUCCESS), + m_state (eStateUnloaded), + m_state_mutex (PTHREAD_MUTEX_RECURSIVE), + m_events (0, kAllEventsMask), + m_breakpoints (), + m_watchpoints (), + m_name_to_addr_callback(NULL), + m_name_to_addr_baton(NULL), + m_image_infos_callback(NULL), + m_image_infos_baton(NULL) +{ + DNBLogThreadedIf(LOG_PROCESS | LOG_VERBOSE, "%s", __PRETTY_FUNCTION__); +} + +MachProcess::~MachProcess() +{ + DNBLogThreadedIf(LOG_PROCESS | LOG_VERBOSE, "%s", __PRETTY_FUNCTION__); + Clear(); +} + +pid_t +MachProcess::SetProcessID(pid_t pid) +{ + // Free any previous process specific data or resources + Clear(); + // Set the current PID appropriately + if (pid == 0) + m_pid == ::getpid (); + else + m_pid = pid; + return m_pid; // Return actualy PID in case a zero pid was passed in +} + +nub_state_t +MachProcess::GetState() +{ + // If any other threads access this we will need a mutex for it + PTHREAD_MUTEX_LOCKER(locker, m_state_mutex); + return m_state; +} + +const char * +MachProcess::ThreadGetName(nub_thread_t tid) +{ + return m_threadList.GetName(tid); +} + +nub_state_t +MachProcess::ThreadGetState(nub_thread_t tid) +{ + return m_threadList.GetState(tid); +} + + +nub_size_t +MachProcess::GetNumThreads () const +{ + return m_threadList.NumThreads(); +} + +nub_thread_t +MachProcess::GetThreadAtIndex (nub_size_t thread_idx) const +{ + return m_threadList.ThreadIDAtIndex(thread_idx); +} + +uint32_t +MachProcess::GetThreadIndexFromThreadID (nub_thread_t tid) +{ + return m_threadList.GetThreadIndexByID(tid); +} + +nub_thread_t +MachProcess::GetCurrentThread () +{ + return m_threadList.CurrentThreadID(); +} + +nub_thread_t +MachProcess::SetCurrentThread(nub_thread_t tid) +{ + return m_threadList.SetCurrentThread(tid); +} + +bool +MachProcess::GetThreadStoppedReason(nub_thread_t tid, struct DNBThreadStopInfo *stop_info) const +{ + return m_threadList.GetThreadStoppedReason(tid, stop_info); +} + +void +MachProcess::DumpThreadStoppedReason(nub_thread_t tid) const +{ + return m_threadList.DumpThreadStoppedReason(tid); +} + +const char * +MachProcess::GetThreadInfo(nub_thread_t tid) const +{ + return m_threadList.GetThreadInfo(tid); +} + +const DNBRegisterSetInfo * +MachProcess::GetRegisterSetInfo(nub_thread_t tid, nub_size_t *num_reg_sets ) const +{ + return DNBArch::GetRegisterSetInfo (num_reg_sets); +} + +bool +MachProcess::GetRegisterValue ( nub_thread_t tid, uint32_t set, uint32_t reg, DNBRegisterValue *value ) const +{ + return m_threadList.GetRegisterValue(tid, set, reg, value); +} + +bool +MachProcess::SetRegisterValue ( nub_thread_t tid, uint32_t set, uint32_t reg, const DNBRegisterValue *value ) const +{ + return m_threadList.SetRegisterValue(tid, set, reg, value); +} + +void +MachProcess::SetState(nub_state_t new_state) +{ + // If any other threads access this we will need a mutex for it + uint32_t event_mask = 0; + + // Scope for mutex locker + { + PTHREAD_MUTEX_LOCKER(locker, m_state_mutex); + DNBLogThreadedIf(LOG_PROCESS, "MachProcess::SetState ( %s )", DNBStateAsString(new_state)); + + const nub_state_t old_state = m_state; + + if (old_state != new_state) + { + if (NUB_STATE_IS_STOPPED(new_state)) + event_mask = eEventProcessStoppedStateChanged; + else + event_mask = eEventProcessRunningStateChanged; + + m_state = new_state; + if (new_state == eStateStopped) + m_stop_count++; + } + } + + if (event_mask != 0) + { + m_events.SetEvents (event_mask); + + // Wait for the event bit to reset if a reset ACK is requested + m_events.WaitForResetAck(event_mask); + } + +} + +void +MachProcess::Clear() +{ + // Clear any cached thread list while the pid and task are still valid + + m_task.Clear(); + // Now clear out all member variables + m_pid = INVALID_NUB_PROCESS; + CloseChildFileDescriptors(); + m_path.clear(); + m_args.clear(); + SetState(eStateUnloaded); + m_flags = eMachProcessFlagsNone; + m_stop_count = 0; + m_threadList.Clear(); + { + PTHREAD_MUTEX_LOCKER(locker, m_exception_messages_mutex); + m_exception_messages.clear(); + } + m_err.Clear(); + +} + + +bool +MachProcess::StartSTDIOThread() +{ + DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s ( )", __FUNCTION__); + // Create the thread that watches for the child STDIO + m_err = ::pthread_create (&m_stdio_thread, NULL, MachProcess::STDIOThread, this); + return m_err.Success(); +} + + +nub_addr_t +MachProcess::LookupSymbol(const char *name, const char *shlib) +{ + if (m_name_to_addr_callback != NULL && name && name[0]) + return m_name_to_addr_callback(ProcessID(), name, shlib, m_name_to_addr_baton); + return INVALID_NUB_ADDRESS; +} + +bool +MachProcess::Resume (const DNBThreadResumeActions& thread_actions) +{ + DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Resume ()"); + nub_state_t state = GetState(); + + if (CanResume(state)) + { + PrivateResume(thread_actions); + } + else if (state == eStateRunning) + { + DNBLogThreadedIf(LOG_PROCESS, "Resume() - task 0x%x is running, ignoring...", m_task.TaskPort()); + m_err.Clear(); + + } + else + { + DNBLogThreadedIf(LOG_PROCESS, "Resume() - task 0x%x can't continue, ignoring...", m_task.TaskPort()); + m_err.SetError(UINT_MAX, DNBError::Generic); + } + return m_err.Success(); +} + +bool +MachProcess::Kill (const struct timespec *timeout_abstime) +{ + DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Kill ()"); + nub_state_t state = DoSIGSTOP(true); + DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Kill() DoSIGSTOP() state = %s", DNBStateAsString(state)); + errno = 0; + ::ptrace (PT_KILL, m_pid, 0, 0); + m_err.SetErrorToErrno(); + DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Kill() DoSIGSTOP() ::ptrace (PT_KILL, pid=%u, 0, 0) => 0x%8.8x (%s)", m_err.Error(), m_err.AsString()); + PrivateResume (DNBThreadResumeActions (eStateRunning, 0)); + return true; +} + +bool +MachProcess::Signal (int signal, const struct timespec *timeout_abstime) +{ + DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Signal (signal = %d, timeout = %p)", signal, timeout_abstime); + nub_state_t state = GetState(); + if (::kill (ProcessID(), signal) == 0) + { + m_err.Clear(); + // If we were running and we have a timeout, wait for the signal to stop + if (IsRunning(state) && timeout_abstime) + { + DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Signal (signal = %d, timeout = %p) waiting for signal to stop process...", signal, timeout_abstime); + m_events.WaitForSetEvents(eEventProcessStoppedStateChanged, timeout_abstime); + state = GetState(); + DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Signal (signal = %d, timeout = %p) state = %s", signal, timeout_abstime, DNBStateAsString(state)); + return !IsRunning (state); + } + DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Signal (signal = %d, timeout = %p) not waiting...", signal, timeout_abstime); + } + else + { + m_err.SetError(errno, DNBError::POSIX); + m_err.LogThreadedIfError("kill (pid = %d, signo = %i)", ProcessID(), signal); + } + return m_err.Success(); + +} + +nub_state_t +MachProcess::DoSIGSTOP (bool clear_bps_and_wps) +{ + nub_state_t state = GetState(); + DNBLogThreadedIf(LOG_PROCESS, "MachProcess::DoSIGSTOP() state = %s", DNBStateAsString (state)); + + if (!IsRunning(state)) + { + if (clear_bps_and_wps) + { + DisableAllBreakpoints (true); + DisableAllWatchpoints (true); + clear_bps_and_wps = false; + } + + // Resume our process + DNBLogThreadedIf(LOG_PROCESS, "MachProcess::DoSIGSTOP() state = %s -- resuming process", DNBStateAsString (state)); + PrivateResume (DNBThreadResumeActions (eStateRunning, 0)); + + // Reset the event that says we were indeed running + m_events.ResetEvents(eEventProcessRunningStateChanged); + state = GetState(); + } + + // We need to be stopped in order to be able to detach, so we need + // to send ourselves a SIGSTOP + + DNBLogThreadedIf(LOG_PROCESS, "MachProcess::DoSIGSTOP() state = %s -- sending SIGSTOP", DNBStateAsString (state)); + struct timespec sigstop_timeout; + DNBTimer::OffsetTimeOfDay(&sigstop_timeout, 2, 0); + Signal (SIGSTOP, &sigstop_timeout); + if (clear_bps_and_wps) + { + DisableAllBreakpoints (true); + DisableAllWatchpoints (true); + clear_bps_and_wps = false; + } + return GetState(); +} + +bool +MachProcess::Detach() +{ + DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Detach()"); + + nub_state_t state = DoSIGSTOP(true); + DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Detach() DoSIGSTOP() returned %s", DNBStateAsString(state)); + + + // Don't reply to our SIGSTOP exception, just make sure no threads + // are still suspended. + PrivateResume (DNBThreadResumeActions (eStateRunning, 0)); + + + // Detach from our process + errno = 0; + nub_process_t pid = m_pid; + ::ptrace (PT_DETACH, pid, (caddr_t)1, 0); + m_err.SetError (errno, DNBError::POSIX); + if (DNBLogCheckLogBit(LOG_PROCESS) || m_err.Fail()) + m_err.LogThreaded("::ptrace (PT_DETACH, %u, (caddr_t)1, 0)", pid); + + // Resume our task + m_task.Resume(); + + SetState(eStateDetached); + + // NULL our task out as we have already retored all exception ports + m_task.Clear(); + + // Clear out any notion of the process we once were + Clear(); + return true; +} + + +nub_size_t +MachProcess::RemoveTrapsFromBuffer (nub_addr_t addr, nub_size_t size, uint8_t *buf) const +{ + nub_size_t bytes_removed = 0; + const DNBBreakpoint *bp; + nub_addr_t intersect_addr; + nub_size_t intersect_size; + nub_size_t opcode_offset; + nub_size_t idx; + for (idx = 0; (bp = m_breakpoints.GetByIndex(idx)) != NULL; ++idx) + { + if (bp->IntersectsRange(addr, size, &intersect_addr, &intersect_size, &opcode_offset)) + { + assert(addr <= intersect_addr && intersect_addr < addr + size); + assert(addr < intersect_addr + intersect_size && intersect_addr + intersect_size <= addr + size); + assert(opcode_offset + intersect_size <= bp->ByteSize()); + nub_size_t buf_offset = intersect_addr - addr; + ::memcpy(buf + buf_offset, bp->SavedOpcodeBytes() + opcode_offset, intersect_size); + } + } + return bytes_removed; +} + +//---------------------------------------------------------------------- +// ReadMemory from the MachProcess level will always remove any software +// breakpoints from the memory buffer before returning. If you wish to +// read memory and see those traps, read from the MachTask +// (m_task.ReadMemory()) as that version will give you what is actually +// in inferior memory. +//---------------------------------------------------------------------- +nub_size_t +MachProcess::ReadMemory (nub_addr_t addr, nub_size_t size, void *buf) +{ + // We need to remove any current software traps (enabled software + // breakpoints) that we may have placed in our tasks memory. + + // First just read the memory as is + nub_size_t bytes_read = m_task.ReadMemory(addr, size, buf); + + // Then place any opcodes that fall into this range back into the buffer + // before we return this to callers. + if (bytes_read > 0) + RemoveTrapsFromBuffer (addr, size, (uint8_t *)buf); + return bytes_read; +} + +//---------------------------------------------------------------------- +// WriteMemory from the MachProcess level will always write memory around +// any software breakpoints. Any software breakpoints will have their +// opcodes modified if they are enabled. Any memory that doesn't overlap +// with software breakpoints will be written to. If you wish to write to +// inferior memory without this interference, then write to the MachTask +// (m_task.WriteMemory()) as that version will always modify inferior +// memory. +//---------------------------------------------------------------------- +nub_size_t +MachProcess::WriteMemory (nub_addr_t addr, nub_size_t size, const void *buf) +{ + // We need to write any data that would go where any current software traps + // (enabled software breakpoints) any software traps (breakpoints) that we + // may have placed in our tasks memory. + + std::map<nub_addr_t, DNBBreakpoint *> addr_to_bp_map; + DNBBreakpoint *bp; + nub_size_t idx; + for (idx = 0; (bp = m_breakpoints.GetByIndex(idx)) != NULL; ++idx) + { + if (bp->IntersectsRange(addr, size, NULL, NULL, NULL)) + addr_to_bp_map[bp->Address()] = bp; + } + + // If we don't have any software breakpoints that are in this buffer, then + // we can just write memory and be done with it. + if (addr_to_bp_map.empty()) + return m_task.WriteMemory(addr, size, buf); + + // If we make it here, we have some breakpoints that overlap and we need + // to work around them. + + nub_size_t bytes_written = 0; + nub_addr_t intersect_addr; + nub_size_t intersect_size; + nub_size_t opcode_offset; + const uint8_t *ubuf = (const uint8_t *)buf; + std::map<nub_addr_t, DNBBreakpoint *>::iterator pos, end = addr_to_bp_map.end(); + for (pos = addr_to_bp_map.begin(); pos != end; ++pos) + { + bp = pos->second; + + assert(bp->IntersectsRange(addr, size, &intersect_addr, &intersect_size, &opcode_offset)); + assert(addr <= intersect_addr && intersect_addr < addr + size); + assert(addr < intersect_addr + intersect_size && intersect_addr + intersect_size <= addr + size); + assert(opcode_offset + intersect_size <= bp->ByteSize()); + + // Check for bytes before this breakpoint + const nub_addr_t curr_addr = addr + bytes_written; + if (intersect_addr > curr_addr) + { + // There are some bytes before this breakpoint that we need to + // just write to memory + nub_size_t curr_size = intersect_addr - curr_addr; + nub_size_t curr_bytes_written = m_task.WriteMemory(curr_addr, curr_size, ubuf + bytes_written); + bytes_written += curr_bytes_written; + if (curr_bytes_written != curr_size) + { + // We weren't able to write all of the requested bytes, we + // are done looping and will return the number of bytes that + // we have written so far. + break; + } + } + + // Now write any bytes that would cover up any software breakpoints + // directly into the breakpoint opcode buffer + ::memcpy(bp->SavedOpcodeBytes() + opcode_offset, ubuf + bytes_written, intersect_size); + bytes_written += intersect_size; + } + + // Write any remaining bytes after the last breakpoint if we have any left + if (bytes_written < size) + bytes_written += m_task.WriteMemory(addr + bytes_written, size - bytes_written, ubuf + bytes_written); + + return bytes_written; +} + + +void +MachProcess::ReplyToAllExceptions (const DNBThreadResumeActions& thread_actions) +{ + PTHREAD_MUTEX_LOCKER(locker, m_exception_messages_mutex); + if (m_exception_messages.empty() == false) + { + MachException::Message::iterator pos; + MachException::Message::iterator begin = m_exception_messages.begin(); + MachException::Message::iterator end = m_exception_messages.end(); + for (pos = begin; pos != end; ++pos) + { + DNBLogThreadedIf(LOG_EXCEPTIONS, "Replying to exception %d...", std::distance(begin, pos)); + int thread_reply_signal = 0; + + const DNBThreadResumeAction *action = thread_actions.GetActionForThread (pos->state.thread_port, false); + + if (action) + { + thread_reply_signal = action->signal; + if (thread_reply_signal) + thread_actions.SetSignalHandledForThread (pos->state.thread_port); + } + + m_err = pos->Reply(this, thread_reply_signal); + if (DNBLogCheckLogBit(LOG_EXCEPTIONS)) + m_err.LogThreadedIfError("Error replying to exception"); + } + + // Erase all exception message as we should have used and replied + // to them all already. + m_exception_messages.clear(); + } +} +void +MachProcess::PrivateResume (const DNBThreadResumeActions& thread_actions) +{ + PTHREAD_MUTEX_LOCKER (locker, m_exception_messages_mutex); + + ReplyToAllExceptions (thread_actions); +// bool stepOverBreakInstruction = step; + + // Let the thread prepare to resume and see if any threads want us to + // step over a breakpoint instruction (ProcessWillResume will modify + // the value of stepOverBreakInstruction). + m_threadList.ProcessWillResume (this, thread_actions); + + // Set our state accordingly + if (thread_actions.NumActionsWithState(eStateStepping)) + SetState (eStateStepping); + else + SetState (eStateRunning); + + // Now resume our task. + m_err = m_task.Resume(); + +} + +nub_break_t +MachProcess::CreateBreakpoint(nub_addr_t addr, nub_size_t length, bool hardware, thread_t tid) +{ + DNBLogThreadedIf(LOG_BREAKPOINTS, "MachProcess::CreateBreakpoint ( addr = 0x%8.8llx, length = %u, hardware = %i, tid = 0x%4.4x )", (uint64_t)addr, length, hardware, tid); + if (hardware && tid == INVALID_NUB_THREAD) + tid = GetCurrentThread(); + + DNBBreakpoint bp(addr, length, tid, hardware); + nub_break_t breakID = m_breakpoints.Add(bp); + if (EnableBreakpoint(breakID)) + { + DNBLogThreadedIf(LOG_BREAKPOINTS, "MachProcess::CreateBreakpoint ( addr = 0x%8.8llx, length = %u, tid = 0x%4.4x ) => %u", (uint64_t)addr, length, tid, breakID); + return breakID; + } + else + { + m_breakpoints.Remove(breakID); + } + // We failed to enable the breakpoint + return INVALID_NUB_BREAK_ID; +} + +nub_watch_t +MachProcess::CreateWatchpoint(nub_addr_t addr, nub_size_t length, uint32_t watch_flags, bool hardware, thread_t tid) +{ + DNBLogThreadedIf(LOG_WATCHPOINTS, "MachProcess::CreateWatchpoint ( addr = 0x%8.8llx, length = %u, flags = 0x%8.8x, hardware = %i, tid = 0x%4.4x )", (uint64_t)addr, length, watch_flags, hardware, tid); + if (hardware && tid == INVALID_NUB_THREAD) + tid = GetCurrentThread(); + + DNBBreakpoint watch(addr, length, tid, hardware); + watch.SetIsWatchpoint(watch_flags); + + nub_watch_t watchID = m_watchpoints.Add(watch); + if (EnableWatchpoint(watchID)) + { + DNBLogThreadedIf(LOG_WATCHPOINTS, "MachProcess::CreateWatchpoint ( addr = 0x%8.8llx, length = %u, tid = 0x%x) => %u", (uint64_t)addr, length, tid, watchID); + return watchID; + } + else + { + DNBLogThreadedIf(LOG_WATCHPOINTS, "MachProcess::CreateWatchpoint ( addr = 0x%8.8llx, length = %u, tid = 0x%x) => FAILED (%u)", (uint64_t)addr, length, tid, watchID); + m_watchpoints.Remove(watchID); + } + // We failed to enable the watchpoint + return INVALID_NUB_BREAK_ID; +} + +nub_size_t +MachProcess::DisableAllBreakpoints(bool remove) +{ + DNBLogThreadedIf(LOG_BREAKPOINTS, "MachProcess::%s (remove = %d )", __FUNCTION__, remove); + DNBBreakpoint *bp; + nub_size_t disabled_count = 0; + nub_size_t idx = 0; + while ((bp = m_breakpoints.GetByIndex(idx)) != NULL) + { + bool success = DisableBreakpoint(bp->GetID(), remove); + + if (success) + disabled_count++; + // If we failed to disable the breakpoint or we aren't removing the breakpoint + // increment the breakpoint index. Otherwise DisableBreakpoint will have removed + // the breakpoint at this index and we don't need to change it. + if ((success == false) || (remove == false)) + idx++; + } + return disabled_count; +} + +nub_size_t +MachProcess::DisableAllWatchpoints(bool remove) +{ + DNBLogThreadedIf(LOG_WATCHPOINTS, "MachProcess::%s (remove = %d )", __FUNCTION__, remove); + DNBBreakpoint *wp; + nub_size_t disabled_count = 0; + nub_size_t idx = 0; + while ((wp = m_watchpoints.GetByIndex(idx)) != NULL) + { + bool success = DisableWatchpoint(wp->GetID(), remove); + + if (success) + disabled_count++; + // If we failed to disable the watchpoint or we aren't removing the watchpoint + // increment the watchpoint index. Otherwise DisableWatchpoint will have removed + // the watchpoint at this index and we don't need to change it. + if ((success == false) || (remove == false)) + idx++; + } + return disabled_count; +} + +bool +MachProcess::DisableBreakpoint(nub_break_t breakID, bool remove) +{ + DNBBreakpoint *bp = m_breakpoints.FindByID (breakID); + if (bp) + { + nub_addr_t addr = bp->Address(); + DNBLogThreadedIf(LOG_BREAKPOINTS | LOG_VERBOSE, "MachProcess::DisableBreakpoint ( breakID = %d, remove = %d ) addr = 0x%8.8llx", breakID, remove, (uint64_t)addr); + + if (bp->IsHardware()) + { + bool hw_disable_result = m_threadList.DisableHardwareBreakpoint (bp); + + if (hw_disable_result == true) + { + bp->SetEnabled(false); + // Let the thread list know that a breakpoint has been modified + if (remove) + { + m_threadList.NotifyBreakpointChanged(bp); + m_breakpoints.Remove(breakID); + } + DNBLogThreadedIf(LOG_BREAKPOINTS, "MachProcess::DisableBreakpoint ( breakID = %d, remove = %d ) addr = 0x%8.8llx (hardware) => success", breakID, remove, (uint64_t)addr); + return true; + } + + return false; + } + + const nub_size_t break_op_size = bp->ByteSize(); + assert (break_op_size > 0); + const uint8_t * const break_op = DNBArch::SoftwareBreakpointOpcode(bp->ByteSize()); + if (break_op_size > 0) + { + // Clear a software breakoint instruction + uint8_t curr_break_op[break_op_size]; + bool break_op_found = false; + + // Read the breakpoint opcode + if (m_task.ReadMemory(addr, break_op_size, curr_break_op) == break_op_size) + { + bool verify = false; + if (bp->IsEnabled()) + { + // Make sure we have the a breakpoint opcode exists at this address + if (memcmp(curr_break_op, break_op, break_op_size) == 0) + { + break_op_found = true; + // We found a valid breakpoint opcode at this address, now restore + // the saved opcode. + if (m_task.WriteMemory(addr, break_op_size, bp->SavedOpcodeBytes()) == break_op_size) + { + verify = true; + } + else + { + DNBLogError("MachProcess::DisableBreakpoint ( breakID = %d, remove = %d ) addr = 0x%8.8llx memory write failed when restoring original opcode", breakID, remove, (uint64_t)addr); + } + } + else + { + DNBLogWarning("MachProcess::DisableBreakpoint ( breakID = %d, remove = %d ) addr = 0x%8.8llx expected a breakpoint opcode but didn't find one.", breakID, remove, (uint64_t)addr); + // Set verify to true and so we can check if the original opcode has already been restored + verify = true; + } + } + else + { + DNBLogThreadedIf(LOG_BREAKPOINTS | LOG_VERBOSE, "MachProcess::DisableBreakpoint ( breakID = %d, remove = %d ) addr = 0x$8.8llx is not enabled", breakID, remove, (uint64_t)addr); + // Set verify to true and so we can check if the original opcode is there + verify = true; + } + + if (verify) + { + uint8_t verify_opcode[break_op_size]; + // Verify that our original opcode made it back to the inferior + if (m_task.ReadMemory(addr, break_op_size, verify_opcode) == break_op_size) + { + // compare the memory we just read with the original opcode + if (memcmp(bp->SavedOpcodeBytes(), verify_opcode, break_op_size) == 0) + { + // SUCCESS + bp->SetEnabled(false); + // Let the thread list know that a breakpoint has been modified + if (remove) + { + m_threadList.NotifyBreakpointChanged(bp); + m_breakpoints.Remove(breakID); + } + DNBLogThreadedIf(LOG_BREAKPOINTS, "MachProcess::DisableBreakpoint ( breakID = %d, remove = %d ) addr = 0x%8.8llx => success", breakID, remove, (uint64_t)addr); + return true; + } + else + { + if (break_op_found) + DNBLogError("MachProcess::DisableBreakpoint ( breakID = %d, remove = %d ) addr = 0x%8.8llx: failed to restore original opcode", breakID, remove, (uint64_t)addr); + else + DNBLogError("MachProcess::DisableBreakpoint ( breakID = %d, remove = %d ) addr = 0x%8.8llx: opcode changed", breakID, remove, (uint64_t)addr); + } + } + else + { + DNBLogWarning("MachProcess::DisableBreakpoint: unable to disable breakpoint 0x%8.8llx", (uint64_t)addr); + } + } + } + else + { + DNBLogWarning("MachProcess::DisableBreakpoint: unable to read memory at 0x%8.8llx", (uint64_t)addr); + } + } + } + else + { + DNBLogError("MachProcess::DisableBreakpoint ( breakID = %d, remove = %d ) invalid breakpoint ID", breakID, remove); + } + return false; +} + +bool +MachProcess::DisableWatchpoint(nub_watch_t watchID, bool remove) +{ + DNBLogThreadedIf(LOG_WATCHPOINTS, "MachProcess::%s(watchID = %d, remove = %d)", __FUNCTION__, watchID, remove); + DNBBreakpoint *wp = m_watchpoints.FindByID (watchID); + if (wp) + { + nub_addr_t addr = wp->Address(); + DNBLogThreadedIf(LOG_WATCHPOINTS, "MachProcess::DisableWatchpoint ( watchID = %d, remove = %d ) addr = 0x%8.8llx", watchID, remove, (uint64_t)addr); + + if (wp->IsHardware()) + { + bool hw_disable_result = m_threadList.DisableHardwareWatchpoint (wp); + + if (hw_disable_result == true) + { + wp->SetEnabled(false); + if (remove) + m_watchpoints.Remove(watchID); + DNBLogThreadedIf(LOG_WATCHPOINTS, "MachProcess::Disablewatchpoint ( watchID = %d, remove = %d ) addr = 0x%8.8llx (hardware) => success", watchID, remove, (uint64_t)addr); + return true; + } + } + + // TODO: clear software watchpoints if we implement them + } + else + { + DNBLogError("MachProcess::DisableWatchpoint ( watchID = %d, remove = %d ) invalid watchpoint ID", watchID, remove); + } + return false; +} + + +void +MachProcess::DumpBreakpoint(nub_break_t breakID) const +{ + DNBLogThreaded("MachProcess::DumpBreakpoint(breakID = %d)", breakID); + + if (NUB_BREAK_ID_IS_VALID(breakID)) + { + const DNBBreakpoint *bp = m_breakpoints.FindByID(breakID); + if (bp) + bp->Dump(); + else + DNBLog("MachProcess::DumpBreakpoint(breakID = %d): invalid breakID", breakID); + } + else + { + m_breakpoints.Dump(); + } +} + +void +MachProcess::DumpWatchpoint(nub_watch_t watchID) const +{ + DNBLogThreaded("MachProcess::DumpWatchpoint(watchID = %d)", watchID); + + if (NUB_BREAK_ID_IS_VALID(watchID)) + { + const DNBBreakpoint *wp = m_watchpoints.FindByID(watchID); + if (wp) + wp->Dump(); + else + DNBLog("MachProcess::DumpWatchpoint(watchID = %d): invalid watchID", watchID); + } + else + { + m_watchpoints.Dump(); + } +} + +bool +MachProcess::EnableBreakpoint(nub_break_t breakID) +{ + DNBLogThreadedIf(LOG_BREAKPOINTS, "MachProcess::EnableBreakpoint ( breakID = %d )", breakID); + DNBBreakpoint *bp = m_breakpoints.FindByID (breakID); + if (bp) + { + nub_addr_t addr = bp->Address(); + if (bp->IsEnabled()) + { + DNBLogWarning("MachProcess::EnableBreakpoint ( breakID = %d ) addr = 0x%8.8llx: breakpoint already enabled.", breakID, (uint64_t)addr); + return true; + } + else + { + if (bp->HardwarePreferred()) + { + bp->SetHardwareIndex(m_threadList.EnableHardwareBreakpoint(bp)); + if (bp->IsHardware()) + { + bp->SetEnabled(true); + return true; + } + } + + const nub_size_t break_op_size = bp->ByteSize(); + assert (break_op_size != 0); + const uint8_t * const break_op = DNBArch::SoftwareBreakpointOpcode(break_op_size); + if (break_op_size > 0) + { + // Save the original opcode by reading it + if (m_task.ReadMemory(addr, break_op_size, bp->SavedOpcodeBytes()) == break_op_size) + { + // Write a software breakpoint in place of the original opcode + if (m_task.WriteMemory(addr, break_op_size, break_op) == break_op_size) + { + uint8_t verify_break_op[4]; + if (m_task.ReadMemory(addr, break_op_size, verify_break_op) == break_op_size) + { + if (memcmp(break_op, verify_break_op, break_op_size) == 0) + { + bp->SetEnabled(true); + // Let the thread list know that a breakpoint has been modified + m_threadList.NotifyBreakpointChanged(bp); + DNBLogThreadedIf(LOG_BREAKPOINTS, "MachProcess::EnableBreakpoint ( breakID = %d ) addr = 0x%8.8llx: SUCCESS.", breakID, (uint64_t)addr); + return true; + } + else + { + DNBLogError("MachProcess::EnableBreakpoint ( breakID = %d ) addr = 0x%8.8llx: breakpoint opcode verification failed.", breakID, (uint64_t)addr); + } + } + else + { + DNBLogError("MachProcess::EnableBreakpoint ( breakID = %d ) addr = 0x%8.8llx: unable to read memory to verify breakpoint opcode.", breakID, (uint64_t)addr); + } + } + else + { + DNBLogError("MachProcess::EnableBreakpoint ( breakID = %d ) addr = 0x%8.8llx: unable to write breakpoint opcode to memory.", breakID, (uint64_t)addr); + } + } + else + { + DNBLogError("MachProcess::EnableBreakpoint ( breakID = %d ) addr = 0x%8.8llx: unable to read memory at breakpoint address.", breakID, (uint64_t)addr); + } + } + else + { + DNBLogError("MachProcess::EnableBreakpoint ( breakID = %d ) no software breakpoint opcode for current architecture.", breakID); + } + } + } + return false; +} + +bool +MachProcess::EnableWatchpoint(nub_watch_t watchID) +{ + DNBLogThreadedIf(LOG_WATCHPOINTS, "MachProcess::EnableWatchpoint(watchID = %d)", watchID); + DNBBreakpoint *wp = m_watchpoints.FindByID (watchID); + if (wp) + { + nub_addr_t addr = wp->Address(); + if (wp->IsEnabled()) + { + DNBLogWarning("MachProcess::EnableWatchpoint(watchID = %d) addr = 0x%8.8llx: watchpoint already enabled.", watchID, (uint64_t)addr); + return true; + } + else + { + // Currently only try and set hardware watchpoints. + wp->SetHardwareIndex(m_threadList.EnableHardwareWatchpoint(wp)); + if (wp->IsHardware()) + { + wp->SetEnabled(true); + return true; + } + // TODO: Add software watchpoints by doing page protection tricks. + } + } + return false; +} + +// Called by the exception thread when an exception has been received from +// our process. The exception message is completely filled and the exception +// data has already been copied. +void +MachProcess::ExceptionMessageReceived (const MachException::Message& exceptionMessage) +{ + PTHREAD_MUTEX_LOCKER (locker, m_exception_messages_mutex); + + if (m_exception_messages.empty()) + m_task.Suspend(); + + DNBLogThreadedIf(LOG_EXCEPTIONS, "MachProcess::ExceptionMessageReceived ( )"); + + // Use a locker to automatically unlock our mutex in case of exceptions + // Add the exception to our internal exception stack + m_exception_messages.push_back(exceptionMessage); +} + +void +MachProcess::ExceptionMessageBundleComplete() +{ + // We have a complete bundle of exceptions for our child process. + PTHREAD_MUTEX_LOCKER (locker, m_exception_messages_mutex); + DNBLogThreadedIf(LOG_EXCEPTIONS, "%s: %d exception messages.", __PRETTY_FUNCTION__, m_exception_messages.size()); + if (!m_exception_messages.empty()) + { + // Let all threads recover from stopping and do any clean up based + // on the previous thread state (if any). + m_threadList.ProcessDidStop(this); + + // Let each thread know of any exceptions + task_t task = m_task.TaskPort(); + size_t i; + for (i=0; i<m_exception_messages.size(); ++i) + { + // Let the thread list figure use the MachProcess to forward all exceptions + // on down to each thread. + if (m_exception_messages[i].state.task_port == task) + m_threadList.NotifyException(m_exception_messages[i].state); + if (DNBLogCheckLogBit(LOG_EXCEPTIONS)) + m_exception_messages[i].Dump(); + } + + if (DNBLogCheckLogBit(LOG_THREAD)) + m_threadList.Dump(); + + bool step_more = false; + if (m_threadList.ShouldStop(step_more)) + { + // Wait for the eEventProcessRunningStateChanged event to be reset + // before changing state to stopped to avoid race condition with + // very fast start/stops + struct timespec timeout; + //DNBTimer::OffsetTimeOfDay(&timeout, 0, 250 * 1000); // Wait for 250 ms + DNBTimer::OffsetTimeOfDay(&timeout, 1, 0); // Wait for 250 ms + m_events.WaitForEventsToReset(eEventProcessRunningStateChanged, &timeout); + SetState(eStateStopped); + } + else + { + // Resume without checking our current state. + PrivateResume (DNBThreadResumeActions (eStateRunning, 0)); + } + } + else + { + DNBLogThreadedIf(LOG_EXCEPTIONS, "%s empty exception messages bundle.", __PRETTY_FUNCTION__, m_exception_messages.size()); + } +} + +nub_size_t +MachProcess::CopyImageInfos ( struct DNBExecutableImageInfo **image_infos, bool only_changed) +{ + if (m_image_infos_callback != NULL) + return m_image_infos_callback(ProcessID(), image_infos, only_changed, m_image_infos_baton); + return 0; +} + +void +MachProcess::SharedLibrariesUpdated ( ) +{ + uint32_t event_bits = eEventSharedLibsStateChange; + // Set the shared library event bit to let clients know of shared library + // changes + m_events.SetEvents(event_bits); + // Wait for the event bit to reset if a reset ACK is requested + m_events.WaitForResetAck(event_bits); +} + +void +MachProcess::AppendSTDOUT (char* s, size_t len) +{ + DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s (<%d> %s) ...", __FUNCTION__, len, s); + PTHREAD_MUTEX_LOCKER (locker, m_stdio_mutex); + m_stdout_data.append(s, len); + m_events.SetEvents(eEventStdioAvailable); + + // Wait for the event bit to reset if a reset ACK is requested + m_events.WaitForResetAck(eEventStdioAvailable); +} + +size_t +MachProcess::GetAvailableSTDOUT (char *buf, size_t buf_size) +{ + DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s (&%p[%u]) ...", __FUNCTION__, buf, buf_size); + PTHREAD_MUTEX_LOCKER (locker, m_stdio_mutex); + size_t bytes_available = m_stdout_data.size(); + if (bytes_available > 0) + { + if (bytes_available > buf_size) + { + memcpy(buf, m_stdout_data.data(), buf_size); + m_stdout_data.erase(0, buf_size); + bytes_available = buf_size; + } + else + { + memcpy(buf, m_stdout_data.data(), bytes_available); + m_stdout_data.clear(); + } + } + return bytes_available; +} + +nub_addr_t +MachProcess::GetDYLDAllImageInfosAddress () +{ + return m_task.GetDYLDAllImageInfosAddress(m_err); +} + +size_t +MachProcess::GetAvailableSTDERR (char *buf, size_t buf_size) +{ + return 0; +} + +void * +MachProcess::STDIOThread(void *arg) +{ + MachProcess *proc = (MachProcess*) arg; + DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s ( arg = %p ) thread starting...", __FUNCTION__, arg); + + // We start use a base and more options so we can control if we + // are currently using a timeout on the mach_msg. We do this to get a + // bunch of related exceptions on our exception port so we can process + // then together. When we have multiple threads, we can get an exception + // per thread and they will come in consecutively. The main thread loop + // will start by calling mach_msg to without having the MACH_RCV_TIMEOUT + // flag set in the options, so we will wait forever for an exception on + // our exception port. After we get one exception, we then will use the + // MACH_RCV_TIMEOUT option with a zero timeout to grab all other current + // exceptions for our process. After we have received the last pending + // exception, we will get a timeout which enables us to then notify + // our main thread that we have an exception bundle avaiable. We then wait + // for the main thread to tell this exception thread to start trying to get + // exceptions messages again and we start again with a mach_msg read with + // infinite timeout. + DNBError err; + int stdout_fd = proc->GetStdoutFileDescriptor(); + int stderr_fd = proc->GetStderrFileDescriptor(); + if (stdout_fd == stderr_fd) + stderr_fd = -1; + + while (stdout_fd >= 0 || stderr_fd >= 0) + { + ::pthread_testcancel (); + + fd_set read_fds; + FD_ZERO (&read_fds); + if (stdout_fd >= 0) + FD_SET (stdout_fd, &read_fds); + if (stderr_fd >= 0) + FD_SET (stderr_fd, &read_fds); + int nfds = std::max<int>(stdout_fd, stderr_fd) + 1; + + int num_set_fds = select (nfds, &read_fds, NULL, NULL, NULL); + DNBLogThreadedIf(LOG_PROCESS, "select (nfds, &read_fds, NULL, NULL, NULL) => %d", num_set_fds); + + if (num_set_fds < 0) + { + int select_errno = errno; + if (DNBLogCheckLogBit(LOG_PROCESS)) + { + err.SetError (select_errno, DNBError::POSIX); + err.LogThreadedIfError("select (nfds, &read_fds, NULL, NULL, NULL) => %d", num_set_fds); + } + + switch (select_errno) + { + case EAGAIN: // The kernel was (perhaps temporarily) unable to allocate the requested number of file descriptors, or we have non-blocking IO + break; + case EBADF: // One of the descriptor sets specified an invalid descriptor. + return NULL; + break; + case EINTR: // A signal was delivered before the time limit expired and before any of the selected events occurred. + case EINVAL: // The specified time limit is invalid. One of its components is negative or too large. + default: // Other unknown error + break; + } + } + else if (num_set_fds == 0) + { + } + else + { + char s[1024]; + s[sizeof(s)-1] = '\0'; // Ensure we have NULL termination + int bytes_read = 0; + if (stdout_fd >= 0 && FD_ISSET (stdout_fd, &read_fds)) + { + do + { + bytes_read = ::read (stdout_fd, s, sizeof(s)-1); + if (bytes_read < 0) + { + int read_errno = errno; + DNBLogThreadedIf(LOG_PROCESS, "read (stdout_fd, ) => %d errno: %d (%s)", bytes_read, read_errno, strerror(read_errno)); + } + else if (bytes_read == 0) + { + // EOF... + DNBLogThreadedIf(LOG_PROCESS, "read (stdout_fd, ) => %d (reached EOF for child STDOUT)", bytes_read); + stdout_fd = -1; + } + else if (bytes_read > 0) + { + proc->AppendSTDOUT(s, bytes_read); + } + + } while (bytes_read > 0); + } + + if (stderr_fd >= 0 && FD_ISSET (stderr_fd, &read_fds)) + { + do + { + bytes_read = ::read (stderr_fd, s, sizeof(s)-1); + if (bytes_read < 0) + { + int read_errno = errno; + DNBLogThreadedIf(LOG_PROCESS, "read (stderr_fd, ) => %d errno: %d (%s)", bytes_read, read_errno, strerror(read_errno)); + } + else if (bytes_read == 0) + { + // EOF... + DNBLogThreadedIf(LOG_PROCESS, "read (stderr_fd, ) => %d (reached EOF for child STDERR)", bytes_read); + stderr_fd = -1; + } + else if (bytes_read > 0) + { + proc->AppendSTDOUT(s, bytes_read); + } + + } while (bytes_read > 0); + } + } + } + DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s (%p): thread exiting...", __FUNCTION__, arg); + return NULL; +} + +pid_t +MachProcess::AttachForDebug (pid_t pid, char *err_str, size_t err_len) +{ + // Clear out and clean up from any current state + Clear(); + if (pid != 0) + { + // Make sure the process exists... + if (::getpgid (pid) < 0) + { + m_err.SetErrorToErrno(); + const char *err_cstr = m_err.AsString(); + ::snprintf (err_str, err_len, "%s", err_cstr ? err_cstr : "No such process"); + return INVALID_NUB_PROCESS; + } + + SetState(eStateAttaching); + m_pid = pid; + // Let ourselves know we are going to be using SBS if the correct flag bit is set... +#if defined (__arm__) + if (IsSBProcess(pid)) + m_flags |= eMachProcessFlagsUsingSBS; +#endif + if (!m_task.StartExceptionThread(m_err)) + { + const char *err_cstr = m_err.AsString(); + ::snprintf (err_str, err_len, "%s", err_cstr ? err_cstr : "unable to start the exception thread"); + DNBLogThreadedIf(LOG_PROCESS, "error: failed to attach to pid %d", pid); + m_pid = INVALID_NUB_PROCESS; + return INVALID_NUB_PROCESS; + } + + errno = 0; + int err = ptrace (PT_ATTACHEXC, pid, 0, 0); + + if (err < 0) + m_err.SetError(errno); + else + m_err.Clear(); + + if (m_err.Success()) + { + m_flags |= eMachProcessFlagsAttached; + // Sleep a bit to let the exception get received and set our process status + // to stopped. + ::usleep(250000); + DNBLogThreadedIf(LOG_PROCESS, "successfully attached to pid %d", pid); + return m_pid; + } + else + { + ::snprintf (err_str, err_len, "%s", m_err.AsString()); + DNBLogThreadedIf(LOG_PROCESS, "error: failed to attach to pid %d", pid); + } + } + return INVALID_NUB_PROCESS; +} + +// Do the process specific setup for attach. If this returns NULL, then there's no +// platform specific stuff to be done to wait for the attach. If you get non-null, +// pass that token to the CheckForProcess method, and then to CleanupAfterAttach. + +// Call PrepareForAttach before attaching to a process that has not yet launched +// This returns a token that can be passed to CheckForProcess, and to CleanupAfterAttach. +// You should call CleanupAfterAttach to free the token, and do whatever other +// cleanup seems good. + +const void * +MachProcess::PrepareForAttach (const char *path, nub_launch_flavor_t launch_flavor, bool waitfor, DNBError &err_str) +{ +#if defined (__arm__) + // Tell SpringBoard to halt the next launch of this application on startup. + + if (!waitfor) + return NULL; + + const char *app_ext = strstr(path, ".app"); + if (app_ext == NULL) + { + DNBLogThreadedIf(LOG_PROCESS, "%s: path '%s' doesn't contain .app, we can't tell springboard to wait for launch...", path); + return NULL; + } + + if (launch_flavor != eLaunchFlavorSpringBoard + && launch_flavor != eLaunchFlavorDefault) + return NULL; + + std::string app_bundle_path(path, app_ext + strlen(".app")); + + CFStringRef bundleIDCFStr = CopyBundleIDForPath (app_bundle_path.c_str (), err_str); + std::string bundleIDStr; + CFString::UTF8(bundleIDCFStr, bundleIDStr); + DNBLogThreadedIf(LOG_PROCESS, "CopyBundleIDForPath (%s, err_str) returned @\"%s\"", app_bundle_path.c_str (), bundleIDStr.c_str()); + + if (bundleIDCFStr == NULL) + { + return NULL; + } + + SBSApplicationLaunchError sbs_error = 0; + + const char *stdout_err = "/dev/null"; + CFString stdio_path; + stdio_path.SetFileSystemRepresentation (stdout_err); + + DNBLogThreadedIf(LOG_PROCESS, "SBSLaunchApplicationForDebugging ( @\"%s\" , NULL, NULL, NULL, @\"%s\", @\"%s\", SBSApplicationDebugOnNextLaunch | SBSApplicationLaunchWaitForDebugger )", bundleIDStr.c_str(), stdout_err, stdout_err); + sbs_error = SBSLaunchApplicationForDebugging (bundleIDCFStr, + (CFURLRef)NULL, // openURL + NULL, // launch_argv.get(), + NULL, // launch_envp.get(), // CFDictionaryRef environment + stdio_path.get(), + stdio_path.get(), + SBSApplicationDebugOnNextLaunch | SBSApplicationLaunchWaitForDebugger); + + if (sbs_error != SBSApplicationLaunchErrorSuccess) + { + err_str.SetError(sbs_error, DNBError::SpringBoard); + return NULL; + } + + DNBLogThreadedIf(LOG_PROCESS, "Successfully set DebugOnNextLaunch."); + return bundleIDCFStr; +# else + return NULL; +#endif +} + +// Pass in the token you got from PrepareForAttach. If there is a process +// for that token, then the pid will be returned, otherwise INVALID_NUB_PROCESS +// will be returned. + +nub_process_t +MachProcess::CheckForProcess (const void *attach_token) +{ + if (attach_token == NULL) + return INVALID_NUB_PROCESS; + +#if defined (__arm__) + CFStringRef bundleIDCFStr = (CFStringRef) attach_token; + Boolean got_it; + nub_process_t attach_pid; + got_it = SBSProcessIDForDisplayIdentifier(bundleIDCFStr, &attach_pid); + if (got_it) + return attach_pid; + else + return INVALID_NUB_PROCESS; +#endif + return INVALID_NUB_PROCESS; +} + +// Call this to clean up after you have either attached or given up on the attach. +// Pass true for success if you have attached, false if you have not. +// The token will also be freed at this point, so you can't use it after calling +// this method. + +void +MachProcess::CleanupAfterAttach (const void *attach_token, bool success, DNBError &err_str) +{ +#if defined (__arm__) + if (attach_token == NULL) + return; + + // Tell SpringBoard to cancel the debug on next launch of this application + // if we failed to attach + if (!success) + { + SBSApplicationLaunchError sbs_error = 0; + CFStringRef bundleIDCFStr = (CFStringRef) attach_token; + + sbs_error = SBSLaunchApplicationForDebugging (bundleIDCFStr, + (CFURLRef)NULL, + NULL, + NULL, + NULL, + NULL, + SBSApplicationCancelDebugOnNextLaunch); + + if (sbs_error != SBSApplicationLaunchErrorSuccess) + { + err_str.SetError(sbs_error, DNBError::SpringBoard); + return; + } + } + + CFRelease((CFStringRef) attach_token); +#endif +} + +pid_t +MachProcess::LaunchForDebug +( + const char *path, + char const *argv[], + char const *envp[], + const char *stdio_path, + nub_launch_flavor_t launch_flavor, + DNBError &launch_err +) +{ + // Clear out and clean up from any current state + Clear(); + + DNBLogThreadedIf(LOG_PROCESS, "%s( path = '%s', argv = %p, envp = %p, launch_flavor = %u )", __FUNCTION__, path, argv, envp, launch_flavor); + + // Fork a child process for debugging + SetState(eStateLaunching); + + switch (launch_flavor) + { + case eLaunchFlavorForkExec: + m_pid = MachProcess::ForkChildForPTraceDebugging (path, argv, envp, this, launch_err); + break; + + case eLaunchFlavorPosixSpawn: + m_pid = MachProcess::PosixSpawnChildForPTraceDebugging (path, argv, envp, stdio_path, this, launch_err); + break; + +#if defined (__arm__) + + case eLaunchFlavorSpringBoard: + { + const char *app_ext = strstr(path, ".app"); + if (app_ext != NULL) + { + std::string app_bundle_path(path, app_ext + strlen(".app")); + return SBLaunchForDebug (app_bundle_path.c_str(), argv, envp, launch_err); + } + } + break; + +#endif + + default: + // Invalid launch + launch_err.SetError(NUB_GENERIC_ERROR, DNBError::Generic); + return INVALID_NUB_PROCESS; + } + + if (m_pid == INVALID_NUB_PROCESS) + { + // If we don't have a valid process ID and no one has set the error, + // then return a generic error + if (launch_err.Success()) + launch_err.SetError(NUB_GENERIC_ERROR, DNBError::Generic); + } + else + { + m_path = path; + size_t i; + char const *arg; + for (i=0; (arg = argv[i]) != NULL; i++) + m_args.push_back(arg); + + m_task.StartExceptionThread(m_err); + if (m_err.Fail()) + { + if (m_err.AsString() == NULL) + m_err.SetErrorString("unable to start the exception thread"); + ::ptrace (PT_KILL, m_pid, 0, 0); + m_pid = INVALID_NUB_PROCESS; + return INVALID_NUB_PROCESS; + } + + StartSTDIOThread(); + + if (launch_flavor == eLaunchFlavorPosixSpawn) + { + + SetState (eStateAttaching); + errno = 0; + int err = ptrace (PT_ATTACHEXC, m_pid, 0, 0); + if (err == 0) + { + m_flags |= eMachProcessFlagsAttached; + DNBLogThreadedIf(LOG_PROCESS, "successfully spawned pid %d", m_pid); + launch_err.Clear(); + } + else + { + SetState (eStateExited); + DNBError ptrace_err(errno, DNBError::POSIX); + DNBLogThreadedIf(LOG_PROCESS, "error: failed to attach to spawned pid %d (err = %i, errno = %i (%s))", m_pid, err, ptrace_err.Error(), ptrace_err.AsString()); + launch_err.SetError(NUB_GENERIC_ERROR, DNBError::Generic); + } + } + else + { + launch_err.Clear(); + } + } + return m_pid; +} + +pid_t +MachProcess::PosixSpawnChildForPTraceDebugging +( + const char *path, + char const *argv[], + char const *envp[], + const char *stdio_path, + MachProcess* process, + DNBError& err +) +{ + posix_spawnattr_t attr; + DNBLogThreadedIf(LOG_PROCESS, "%s ( path='%s', argv=%p, envp=%p, process )", __FUNCTION__, path, argv, envp); + + err.SetError( ::posix_spawnattr_init (&attr), DNBError::POSIX); + if (err.Fail() || DNBLogCheckLogBit(LOG_PROCESS)) + err.LogThreaded("::posix_spawnattr_init ( &attr )"); + if (err.Fail()) + return INVALID_NUB_PROCESS; + + err.SetError( ::posix_spawnattr_setflags (&attr, POSIX_SPAWN_START_SUSPENDED), DNBError::POSIX); + if (err.Fail() || DNBLogCheckLogBit(LOG_PROCESS)) + err.LogThreaded("::posix_spawnattr_setflags ( &attr, POSIX_SPAWN_START_SUSPENDED )"); + if (err.Fail()) + return INVALID_NUB_PROCESS; + + // Don't do this on SnowLeopard, _sometimes_ the TASK_BASIC_INFO will fail + // and we will fail to continue with our process... + + // On SnowLeopard we should set "DYLD_NO_PIE" in the inferior environment.... + +//#ifndef _POSIX_SPAWN_DISABLE_ASLR +//#define _POSIX_SPAWN_DISABLE_ASLR 0x0100 +//#endif +// err.SetError( ::posix_spawnattr_setflags (&attr, _POSIX_SPAWN_DISABLE_ASLR), DNBError::POSIX); +// if (err.Fail() || DNBLogCheckLogBit(LOG_PROCESS)) +// err.LogThreaded("::posix_spawnattr_setflags ( &attr, _POSIX_SPAWN_DISABLE_ASLR )"); + +#if !defined(__arm__) + + // We don't need to do this for ARM, and we really shouldn't now that we + // have multiple CPU subtypes and no posix_spawnattr call that allows us + // to set which CPU subtype to launch... + cpu_type_t cpu_type = DNBArch::GetCPUType(); + size_t ocount = 0; + err.SetError( ::posix_spawnattr_setbinpref_np (&attr, 1, &cpu_type, &ocount), DNBError::POSIX); + if (err.Fail() || DNBLogCheckLogBit(LOG_PROCESS)) + err.LogThreaded("::posix_spawnattr_setbinpref_np ( &attr, 1, cpu_type = 0x%8.8x, count => %zu )", cpu_type, ocount); + + if (err.Fail() != 0 || ocount != 1) + return INVALID_NUB_PROCESS; + +#endif + + PseudoTerminal pty; + + posix_spawn_file_actions_t file_actions; + err.SetError( ::posix_spawn_file_actions_init (&file_actions), DNBError::POSIX); + int file_actions_valid = err.Success(); + if (!file_actions_valid || DNBLogCheckLogBit(LOG_PROCESS)) + err.LogThreaded("::posix_spawn_file_actions_init ( &file_actions )"); + int pty_error = -1; + pid_t pid = INVALID_NUB_PROCESS; + if (file_actions_valid) + { + if (stdio_path == NULL) + { + pty_error = pty.OpenFirstAvailableMaster(O_RDWR|O_NOCTTY); + if (pty_error == PseudoTerminal::success) + stdio_path = pty.SlaveName(); + // Make sure we were able to get the slave name + if (stdio_path == NULL) + stdio_path = "/dev/null"; + } + + if (stdio_path != NULL) + { + err.SetError( ::posix_spawn_file_actions_addopen(&file_actions, STDERR_FILENO, stdio_path, O_RDWR, 0), DNBError::POSIX); + if (err.Fail() || DNBLogCheckLogBit(LOG_PROCESS)) + err.LogThreaded("::posix_spawn_file_actions_addopen ( &file_actions, filedes = STDERR_FILENO, path = '%s', oflag = O_RDWR, mode = 0 )", stdio_path); + + err.SetError( ::posix_spawn_file_actions_addopen(&file_actions, STDIN_FILENO, stdio_path, O_RDONLY, 0), DNBError::POSIX); + if (err.Fail() || DNBLogCheckLogBit(LOG_PROCESS)) + err.LogThreaded("::posix_spawn_file_actions_addopen ( &file_actions, filedes = STDIN_FILENO, path = '%s', oflag = O_RDONLY, mode = 0 )", stdio_path); + + err.SetError( ::posix_spawn_file_actions_addopen(&file_actions, STDOUT_FILENO, stdio_path, O_WRONLY, 0), DNBError::POSIX); + if (err.Fail() || DNBLogCheckLogBit(LOG_PROCESS)) + err.LogThreaded("::posix_spawn_file_actions_addopen ( &file_actions, filedes = STDOUT_FILENO, path = '%s', oflag = O_WRONLY, mode = 0 )", stdio_path); + } + err.SetError( ::posix_spawnp (&pid, path, &file_actions, &attr, (char * const*)argv, (char * const*)envp), DNBError::POSIX); + if (err.Fail() || DNBLogCheckLogBit(LOG_PROCESS)) + err.LogThreaded("::posix_spawnp ( pid => %i, path = '%s', file_actions = %p, attr = %p, argv = %p, envp = %p )", pid, path, &file_actions, &attr, argv, envp); + } + else + { + err.SetError( ::posix_spawnp (&pid, path, NULL, &attr, (char * const*)argv, (char * const*)envp), DNBError::POSIX); + if (err.Fail() || DNBLogCheckLogBit(LOG_PROCESS)) + err.LogThreaded("::posix_spawnp ( pid => %i, path = '%s', file_actions = %p, attr = %p, argv = %p, envp = %p )", pid, path, NULL, &attr, argv, envp); + } + + // We have seen some cases where posix_spawnp was returning a valid + // looking pid even when an error was returned, so clear it out + if (err.Fail()) + pid = INVALID_NUB_PROCESS; + + if (pty_error == 0) + { + if (process != NULL) + { + int master_fd = pty.ReleaseMasterFD(); + process->SetChildFileDescriptors(master_fd, master_fd, master_fd); + } + } + + if (file_actions_valid) + { + DNBError err2; + err2.SetError( ::posix_spawn_file_actions_destroy (&file_actions), DNBError::POSIX); + if (err2.Fail() || DNBLogCheckLogBit(LOG_PROCESS)) + err2.LogThreaded("::posix_spawn_file_actions_destroy ( &file_actions )"); + } + + return pid; +} + +pid_t +MachProcess::ForkChildForPTraceDebugging +( + const char *path, + char const *argv[], + char const *envp[], + MachProcess* process, + DNBError& launch_err +) +{ + PseudoTerminal::Error pty_error = PseudoTerminal::success; + + // Use a fork that ties the child process's stdin/out/err to a pseudo + // terminal so we can read it in our MachProcess::STDIOThread + // as unbuffered io. + PseudoTerminal pty; + pid_t pid = pty.Fork(pty_error); + + if (pid < 0) + { + //-------------------------------------------------------------- + // Error during fork. + //-------------------------------------------------------------- + return pid; + } + else if (pid == 0) + { + //-------------------------------------------------------------- + // Child process + //-------------------------------------------------------------- + ::ptrace (PT_TRACE_ME, 0, 0, 0); // Debug this process + ::ptrace (PT_SIGEXC, 0, 0, 0); // Get BSD signals as mach exceptions + + // If our parent is setgid, lets make sure we don't inherit those + // extra powers due to nepotism. + ::setgid (getgid ()); + + // Let the child have its own process group. We need to execute + // this call in both the child and parent to avoid a race condition + // between the two processes. + ::setpgid (0, 0); // Set the child process group to match its pid + + // Sleep a bit to before the exec call + ::sleep (1); + + // Turn this process into + ::execv (path, (char * const *)argv); + // Exit with error code. Child process should have taken + // over in above exec call and if the exec fails it will + // exit the child process below. + ::exit (127); + } + else + { + //-------------------------------------------------------------- + // Parent process + //-------------------------------------------------------------- + // Let the child have its own process group. We need to execute + // this call in both the child and parent to avoid a race condition + // between the two processes. + ::setpgid (pid, pid); // Set the child process group to match its pid + + if (process != NULL) + { + // Release our master pty file descriptor so the pty class doesn't + // close it and so we can continue to use it in our STDIO thread + int master_fd = pty.ReleaseMasterFD(); + process->SetChildFileDescriptors(master_fd, master_fd, master_fd); + } + } + return pid; +} + +#if defined (__arm__) + +pid_t +MachProcess::SBLaunchForDebug (const char *path, char const *argv[], char const *envp[], DNBError &launch_err) +{ + // Clear out and clean up from any current state + Clear(); + + DNBLogThreadedIf(LOG_PROCESS, "%s( '%s', argv)", __FUNCTION__, path); + + // Fork a child process for debugging + SetState(eStateLaunching); + m_pid = MachProcess::SBForkChildForPTraceDebugging(path, argv, envp, this, launch_err); + if (m_pid != 0) + { + m_flags |= eMachProcessFlagsUsingSBS; + m_path = path; + size_t i; + char const *arg; + for (i=0; (arg = argv[i]) != NULL; i++) + m_args.push_back(arg); + m_task.StartExceptionThread(); + StartSTDIOThread(); + SetState (eStateAttaching); + int err = ptrace (PT_ATTACHEXC, m_pid, 0, 0); + if (err == 0) + { + m_flags |= eMachProcessFlagsAttached; + DNBLogThreadedIf(LOG_PROCESS, "successfully attached to pid %d", m_pid); + } + else + { + SetState (eStateExited); + DNBLogThreadedIf(LOG_PROCESS, "error: failed to attach to pid %d", m_pid); + } + } + return m_pid; +} + +#include <servers/bootstrap.h> + +// This returns a CFRetained pointer to the Bundle ID for app_bundle_path, +// or NULL if there was some problem getting the bundle id. +static CFStringRef +CopyBundleIDForPath (const char *app_bundle_path, DNBError &err_str) +{ + CFBundle bundle(app_bundle_path); + CFStringRef bundleIDCFStr = bundle.GetIdentifier(); + std::string bundleID; + if (CFString::UTF8(bundleIDCFStr, bundleID) == NULL) + { + struct stat app_bundle_stat; + char err_msg[PATH_MAX]; + + if (::stat (app_bundle_path, &app_bundle_stat) < 0) + { + err_str.SetError(errno, DNBError::POSIX); + snprintf(err_msg, sizeof(err_msg), "%s: \"%s\"", err_str.AsString(), app_bundle_path); + err_str.SetErrorString(err_msg); + DNBLogThreadedIf(LOG_PROCESS, "%s() error: %s", __FUNCTION__, err_msg); + } + else + { + err_str.SetError(-1, DNBError::Generic); + snprintf(err_msg, sizeof(err_msg), "failed to extract CFBundleIdentifier from %s", app_bundle_path); + err_str.SetErrorString(err_msg); + DNBLogThreadedIf(LOG_PROCESS, "%s() error: failed to extract CFBundleIdentifier from '%s'", __FUNCTION__, app_bundle_path); + } + return NULL; + } + + DNBLogThreadedIf(LOG_PROCESS, "%s() extracted CFBundleIdentifier: %s", __FUNCTION__, bundleID.c_str()); + CFRetain (bundleIDCFStr); + + return bundleIDCFStr; +} + +pid_t +MachProcess::SBForkChildForPTraceDebugging (const char *app_bundle_path, char const *argv[], char const *envp[], MachProcess* process, DNBError &launch_err) +{ + DNBLogThreadedIf(LOG_PROCESS, "%s( '%s', argv, %p)", __FUNCTION__, app_bundle_path, process); + CFAllocatorRef alloc = kCFAllocatorDefault; + + if (argv[0] == NULL) + return INVALID_NUB_PROCESS; + + size_t argc = 0; + // Count the number of arguments + while (argv[argc] != NULL) + argc++; + + // Enumerate the arguments + size_t first_launch_arg_idx = 1; + CFReleaser<CFMutableArrayRef> launch_argv; + + if (argv[first_launch_arg_idx]) + { + size_t launch_argc = argc > 0 ? argc - 1 : 0; + launch_argv.reset (::CFArrayCreateMutable (alloc, launch_argc, &kCFTypeArrayCallBacks)); + size_t i; + char const *arg; + CFString launch_arg; + for (i=first_launch_arg_idx; (i < argc) && ((arg = argv[i]) != NULL); i++) + { + launch_arg.reset(::CFStringCreateWithCString (alloc, arg, kCFStringEncodingUTF8)); + if (launch_arg.get() != NULL) + CFArrayAppendValue(launch_argv.get(), launch_arg.get()); + else + break; + } + } + + // Next fill in the arguments dictionary. Note, the envp array is of the form + // Variable=value but SpringBoard wants a CF dictionary. So we have to convert + // this here. + + CFReleaser<CFMutableDictionaryRef> launch_envp; + + if (envp[0]) + { + launch_envp.reset(::CFDictionaryCreateMutable(alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); + const char *value; + int name_len; + CFString name_string, value_string; + + for (int i = 0; envp[i] != NULL; i++) + { + value = strstr (envp[i], "="); + + // If the name field is empty or there's no =, skip it. Somebody's messing with us. + if (value == NULL || value == envp[i]) + continue; + + name_len = value - envp[i]; + + // Now move value over the "=" + value++; + + name_string.reset(::CFStringCreateWithBytes(alloc, (const UInt8 *) envp[i], name_len, kCFStringEncodingUTF8, false)); + value_string.reset(::CFStringCreateWithCString(alloc, value, kCFStringEncodingUTF8)); + CFDictionarySetValue (launch_envp.get(), name_string.get(), value_string.get()); + } + } + + CFString stdio_path; + + PseudoTerminal pty; + PseudoTerminal::Error pty_err = pty.OpenFirstAvailableMaster(O_RDWR|O_NOCTTY); + if (pty_err == PseudoTerminal::success) + { + const char* slave_name = pty.SlaveName(); + DNBLogThreadedIf(LOG_PROCESS, "%s() successfully opened master pty, slave is %s", __FUNCTION__, slave_name); + if (slave_name && slave_name[0]) + { + ::chmod (slave_name, S_IRWXU | S_IRWXG | S_IRWXO); + stdio_path.SetFileSystemRepresentation (slave_name); + } + } + + if (stdio_path.get() == NULL) + { + stdio_path.SetFileSystemRepresentation ("/dev/null"); + } + + CFStringRef bundleIDCFStr = CopyBundleIDForPath (app_bundle_path, launch_err); + if (bundleIDCFStr == NULL) + return INVALID_NUB_PROCESS; + + std::string bundleID; + CFString::UTF8(bundleIDCFStr, bundleID); + + CFData argv_data(NULL); + + if (launch_argv.get()) + { + if (argv_data.Serialize(launch_argv.get(), kCFPropertyListBinaryFormat_v1_0) == NULL) + { + DNBLogThreadedIf(LOG_PROCESS, "%s() error: failed to serialize launch arg array...", __FUNCTION__); + return INVALID_NUB_PROCESS; + } + } + + DNBLogThreadedIf(LOG_PROCESS, "%s() serialized launch arg array", __FUNCTION__); + + // Find SpringBoard + SBSApplicationLaunchError sbs_error = 0; + sbs_error = SBSLaunchApplicationForDebugging (bundleIDCFStr, + (CFURLRef)NULL, // openURL + launch_argv.get(), + launch_envp.get(), // CFDictionaryRef environment + stdio_path.get(), + stdio_path.get(), + SBSApplicationLaunchWaitForDebugger | SBSApplicationLaunchUnlockDevice); + + + launch_err.SetError(sbs_error, DNBError::SpringBoard); + + if (sbs_error == SBSApplicationLaunchErrorSuccess) + { + static const useconds_t pid_poll_interval = 200000; + static const useconds_t pid_poll_timeout = 30000000; + + useconds_t pid_poll_total = 0; + + nub_process_t pid = INVALID_NUB_PROCESS; + Boolean pid_found = SBSProcessIDForDisplayIdentifier(bundleIDCFStr, &pid); + // Poll until the process is running, as long as we are getting valid responses and the timeout hasn't expired + // A return PID of 0 means the process is not running, which may be because it hasn't been (asynchronously) started + // yet, or that it died very quickly (if you weren't using waitForDebugger). + while (!pid_found && pid_poll_total < pid_poll_timeout) + { + usleep (pid_poll_interval); + pid_poll_total += pid_poll_interval; + DNBLogThreadedIf(LOG_PROCESS, "%s() polling Springboard for pid for %s...", __FUNCTION__, bundleID.c_str()); + pid_found = SBSProcessIDForDisplayIdentifier(bundleIDCFStr, &pid); + } + + CFRelease (bundleIDCFStr); + if (pid_found) + { + if (process != NULL) + { + // Release our master pty file descriptor so the pty class doesn't + // close it and so we can continue to use it in our STDIO thread + int master_fd = pty.ReleaseMasterFD(); + process->SetChildFileDescriptors(master_fd, master_fd, master_fd); + } + DNBLogThreadedIf(LOG_PROCESS, "%s() => pid = %4.4x", __FUNCTION__, pid); + } + else + { + DNBLogError("failed to lookup the process ID for CFBundleIdentifier %s.", bundleID.c_str()); + } + return pid; + } + + DNBLogError("unable to launch the application with CFBundleIdentifier '%s' sbs_error = %u", bundleID.c_str(), sbs_error); + return INVALID_NUB_PROCESS; +} + +#endif // #if defined (__arm__) + + diff --git a/lldb/tools/debugserver/source/MacOSX/MachProcess.h b/lldb/tools/debugserver/source/MacOSX/MachProcess.h new file mode 100644 index 00000000000..4d5d0d4af92 --- /dev/null +++ b/lldb/tools/debugserver/source/MacOSX/MachProcess.h @@ -0,0 +1,263 @@ +//===-- MachProcess.h -------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/15/07. +// +//===----------------------------------------------------------------------===// + +#ifndef __MachProcess_h__ +#define __MachProcess_h__ + +#include "DNBDefs.h" +#include "DNBBreakpoint.h" +#include "DNBError.h" +//#include "MachDYLD.h" +#include "MachException.h" +#include "MachVMMemory.h" +#include "MachTask.h" +#include "MachThreadList.h" +#include "PThreadCondition.h" +#include "PThreadEvent.h" +#include "PThreadMutex.h" + +#include <mach/mach.h> +#include <sys/signal.h> +#include <pthread.h> +#include <vector> + +class DNBThreadResumeActions; + +class MachProcess +{ +public: + //---------------------------------------------------------------------- + // Constructors and Destructors + //---------------------------------------------------------------------- + MachProcess (); + ~MachProcess (); + + //---------------------------------------------------------------------- + // Child process control + //---------------------------------------------------------------------- + pid_t AttachForDebug (pid_t pid, char *err_str, size_t err_len); + pid_t LaunchForDebug (const char *path, char const *argv[], char const *envp[], const char *stdio_path, nub_launch_flavor_t launch_flavor, DNBError &err); + static pid_t ForkChildForPTraceDebugging (const char *path, char const *argv[], char const *envp[], MachProcess* process, DNBError &err); + static pid_t PosixSpawnChildForPTraceDebugging (const char *path, char const *argv[], char const *envp[], const char *stdio_path, MachProcess* process, DNBError& err); + nub_addr_t GetDYLDAllImageInfosAddress (); + static const void * PrepareForAttach (const char *path, nub_launch_flavor_t launch_flavor, bool waitfor, DNBError &err_str); + static void CleanupAfterAttach (const void *attach_token, bool success, DNBError &err_str); + static nub_process_t CheckForProcess (const void *attach_token); +#if defined (__arm__) + pid_t SBLaunchForDebug (const char *app_bundle_path, char const *argv[], char const *envp[], DNBError &launch_err); + static pid_t SBForkChildForPTraceDebugging (const char *path, char const *argv[], char const *envp[], MachProcess* process, DNBError &launch_err); +#endif + nub_addr_t LookupSymbol (const char *name, const char *shlib); + void SetNameToAddressCallback (DNBCallbackNameToAddress callback, void *baton) + { + m_name_to_addr_callback = callback; + m_name_to_addr_baton = baton; + } + void SetSharedLibraryInfoCallback (DNBCallbackCopyExecutableImageInfos callback, void *baton) + { + m_image_infos_callback = callback; + m_image_infos_baton = baton; + } + + bool Resume (const DNBThreadResumeActions& thread_actions); + bool Signal (int signal, const struct timespec *timeout_abstime = NULL); + bool Kill (const struct timespec *timeout_abstime = NULL); + bool Detach (); + nub_size_t ReadMemory (nub_addr_t addr, nub_size_t size, void *buf); + nub_size_t WriteMemory (nub_addr_t addr, nub_size_t size, const void *buf); + + //---------------------------------------------------------------------- + // Path and arg accessors + //---------------------------------------------------------------------- + const char * Path () const { return m_path.c_str(); } + size_t ArgumentCount () const { return m_args.size(); } + const char * ArgumentAtIndex (size_t arg_idx) const + { + if (arg_idx < m_args.size()) + return m_args[arg_idx].c_str(); + return NULL; + } + + //---------------------------------------------------------------------- + // Breakpoint functions + //---------------------------------------------------------------------- + nub_break_t CreateBreakpoint (nub_addr_t addr, nub_size_t length, bool hardware, thread_t thread); + bool DisableBreakpoint (nub_break_t breakID, bool remove); + nub_size_t DisableAllBreakpoints (bool remove); + bool EnableBreakpoint (nub_break_t breakID); + void DumpBreakpoint(nub_break_t breakID) const; + DNBBreakpointList& Breakpoints() { return m_breakpoints; } + const DNBBreakpointList& Breakpoints() const { return m_breakpoints; } + + //---------------------------------------------------------------------- + // Watchpoint functions + //---------------------------------------------------------------------- + nub_watch_t CreateWatchpoint (nub_addr_t addr, nub_size_t length, uint32_t watch_type, bool hardware, thread_t thread); + bool DisableWatchpoint (nub_watch_t watchID, bool remove); + nub_size_t DisableAllWatchpoints (bool remove); + bool EnableWatchpoint (nub_watch_t watchID); + void DumpWatchpoint(nub_watch_t watchID) const; + DNBBreakpointList& Watchpoints() { return m_watchpoints; } + const DNBBreakpointList& Watchpoints() const { return m_watchpoints; } + + //---------------------------------------------------------------------- + // Exception thread functions + //---------------------------------------------------------------------- + bool StartSTDIOThread (); + static void * STDIOThread (void *arg); + void ExceptionMessageReceived (const MachException::Message& exceptionMessage); + void ExceptionMessageBundleComplete (); + void SharedLibrariesUpdated (); + nub_size_t CopyImageInfos (struct DNBExecutableImageInfo **image_infos, bool only_changed); + + //---------------------------------------------------------------------- + // Accessors + //---------------------------------------------------------------------- + pid_t ProcessID () const { return m_pid; } + bool ProcessIDIsValid () const { return m_pid > 0; } + pid_t SetProcessID (pid_t pid); + MachTask& Task() { return m_task; } + const MachTask& Task() const { return m_task; } + + PThreadEvent& Events() { return m_events; } + const DNBRegisterSetInfo * + GetRegisterSetInfo (nub_thread_t tid, nub_size_t *num_reg_sets) const; + bool GetRegisterValue (nub_thread_t tid, uint32_t set, uint32_t reg, DNBRegisterValue *reg_value) const; + bool SetRegisterValue (nub_thread_t tid, uint32_t set, uint32_t reg, const DNBRegisterValue *value) const; + const char * ThreadGetName (nub_thread_t tid); + nub_state_t ThreadGetState (nub_thread_t tid); + nub_size_t GetNumThreads () const; + nub_thread_t GetThreadAtIndex (nub_size_t thread_idx) const; + uint32_t GetThreadIndexFromThreadID (nub_thread_t tid); + nub_thread_t GetCurrentThread (); + nub_thread_t SetCurrentThread (nub_thread_t tid); + MachThreadList & GetThreadList() { return m_threadList; } + bool GetThreadStoppedReason(nub_thread_t tid, struct DNBThreadStopInfo *stop_info) const; + void DumpThreadStoppedReason(nub_thread_t tid) const; + const char * GetThreadInfo (nub_thread_t tid) const; + + nub_state_t GetState (); + void SetState (nub_state_t state); + bool IsRunning (nub_state_t state) + { + return state == eStateRunning || IsStepping(state); + } + bool IsStepping (nub_state_t state) + { + return state == eStateStepping; + } + bool CanResume (nub_state_t state) + { + return state == eStateStopped; + } + + const DNBError& GetLastError () const { return m_err; } + + bool GetExitStatus(int* status) + { + if (GetState() == eStateExited) + { + if (status) + *status = m_exit_status; + return true; + } + return false; + } + void SetExitStatus(int status) + { + m_exit_status = status; + SetState(eStateExited); + } + + uint32_t StopCount() const { return m_stop_count; } + void SetChildFileDescriptors (int stdin_fileno, int stdout_fileno, int stderr_fileno) + { + m_child_stdin = stdin_fileno; + m_child_stdout = stdout_fileno; + m_child_stderr = stderr_fileno; + } + + int GetStdinFileDescriptor () const { return m_child_stdin; } + int GetStdoutFileDescriptor () const { return m_child_stdout; } + int GetStderrFileDescriptor () const { return m_child_stderr; } + void AppendSTDOUT (char* s, size_t len); + size_t GetAvailableSTDOUT (char *buf, size_t buf_size); + size_t GetAvailableSTDERR (char *buf, size_t buf_size); + void CloseChildFileDescriptors () + { + if (m_child_stdin >= 0) + { + ::close (m_child_stdin); + m_child_stdin = -1; + } + if (m_child_stdout >= 0) + { + ::close (m_child_stdout); + m_child_stdout = -1; + } + if (m_child_stderr >= 0) + { + ::close (m_child_stderr); + m_child_stderr = -1; + } + } + + bool ProcessUsingSpringBoard() const { return (m_flags & eMachProcessFlagsUsingSBS) != 0; } +private: + enum + { + eMachProcessFlagsNone = 0, + eMachProcessFlagsAttached = (1 << 0), + eMachProcessFlagsUsingSBS = (1 << 1) + }; + void Clear (); + void ReplyToAllExceptions (const DNBThreadResumeActions& thread_actions); + void PrivateResume (const DNBThreadResumeActions& thread_actions); + nub_size_t RemoveTrapsFromBuffer (nub_addr_t addr, nub_size_t size, uint8_t *buf) const; + + uint32_t Flags () const { return m_flags; } + nub_state_t DoSIGSTOP (bool clear_bps_and_wps); + + pid_t m_pid; // Process ID of child process + int m_child_stdin; + int m_child_stdout; + int m_child_stderr; + std::string m_path; // A path to the executable if we have one + std::vector<std::string> m_args; // The arguments with which the process was lauched + int m_exit_status; // The exit status for the process + MachTask m_task; // The mach task for this process + uint32_t m_flags; // Process specific flags (see eMachProcessFlags enums) + uint32_t m_stop_count; // A count of many times have we stopped + pthread_t m_stdio_thread; // Thread ID for the thread that watches for child process stdio + PThreadMutex m_stdio_mutex; // Multithreaded protection for stdio + std::string m_stdout_data; + MachException::Message::collection + m_exception_messages; // A collection of exception messages caught when listening to the exception port + PThreadMutex m_exception_messages_mutex; // Multithreaded protection for m_exception_messages + + MachThreadList m_threadList; // A list of threads that is maintained/updated after each stop + DNBError m_err; // The last error for any transaction + nub_state_t m_state; // The state of our process + PThreadMutex m_state_mutex; // Multithreaded protection for m_state + PThreadEvent m_events; // Process related events in the child processes lifetime can be waited upon + DNBBreakpointList m_breakpoints; // Breakpoint list for this process + DNBBreakpointList m_watchpoints; // Watchpoint list for this process + DNBCallbackNameToAddress m_name_to_addr_callback; + void * m_name_to_addr_baton; + DNBCallbackCopyExecutableImageInfos + m_image_infos_callback; + void * m_image_infos_baton; +}; + + +#endif // __MachProcess_h__ diff --git a/lldb/tools/debugserver/source/MacOSX/MachTask.cpp b/lldb/tools/debugserver/source/MacOSX/MachTask.cpp new file mode 100644 index 00000000000..68d858c014c --- /dev/null +++ b/lldb/tools/debugserver/source/MacOSX/MachTask.cpp @@ -0,0 +1,660 @@ +//===-- MachTask.cpp --------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +//---------------------------------------------------------------------- +// +// MachTask.cpp +// debugserver +// +// Created by Greg Clayton on 12/5/08. +// +//===----------------------------------------------------------------------===// + +#include "MachTask.h" + +// C Includes + +#include <mach-o/dyld_images.h> +#include <mach/mach_vm.h> + +// C++ Includes +// Other libraries and framework includes +// Project includes +#include "CFUtils.h" +#include "DNB.h" +#include "DNBError.h" +#include "DNBLog.h" +#include "MachProcess.h" +#include "DNBDataRef.h" + +#if defined (__arm__) + +#include <CoreFoundation/CoreFoundation.h> +#include <SpringBoardServices/SpringBoardServer.h> +#include <SpringBoardServices/SBSWatchdogAssertion.h> + +#endif + +//---------------------------------------------------------------------- +// MachTask constructor +//---------------------------------------------------------------------- +MachTask::MachTask(MachProcess *process) : + m_process (process), + m_task (TASK_NULL), + m_vm_memory (), + m_exception_thread (0), + m_exception_port (MACH_PORT_NULL) +{ + memset(&m_exc_port_info, 0, sizeof(m_exc_port_info)); + +} + +//---------------------------------------------------------------------- +// Destructor +//---------------------------------------------------------------------- +MachTask::~MachTask() +{ + Clear(); +} + + +//---------------------------------------------------------------------- +// MachTask::Suspend +//---------------------------------------------------------------------- +kern_return_t +MachTask::Suspend() +{ + DNBError err; + task_t task = TaskPort(); + err = ::task_suspend (task); + if (DNBLogCheckLogBit(LOG_TASK) || err.Fail()) + err.LogThreaded("::task_suspend ( target_task = 0x%4.4x )", task); + return err.Error(); +} + + +//---------------------------------------------------------------------- +// MachTask::Resume +//---------------------------------------------------------------------- +kern_return_t +MachTask::Resume() +{ + struct task_basic_info task_info; + task_t task = TaskPort(); + + DNBError err; + err = BasicInfo(task, &task_info); + + if (err.Success()) + { + integer_t i; + for (i=0; i<task_info.suspend_count; i++) + { + err = ::task_resume (task); + if (DNBLogCheckLogBit(LOG_TASK) || err.Fail()) + err.LogThreaded("::task_resume ( target_task = 0x%4.4x )", task); + } + } + return err.Error(); +} + +//---------------------------------------------------------------------- +// MachTask::ExceptionPort +//---------------------------------------------------------------------- +mach_port_t +MachTask::ExceptionPort() const +{ + return m_exception_port; +} + +//---------------------------------------------------------------------- +// MachTask::ExceptionPortIsValid +//---------------------------------------------------------------------- +bool +MachTask::ExceptionPortIsValid() const +{ + return MACH_PORT_VALID(m_exception_port); +} + + +//---------------------------------------------------------------------- +// MachTask::Clear +//---------------------------------------------------------------------- +void +MachTask::Clear() +{ + // Do any cleanup needed for this task + m_task = TASK_NULL; + m_exception_thread = 0; + m_exception_port = MACH_PORT_NULL; + +} + + +//---------------------------------------------------------------------- +// MachTask::SaveExceptionPortInfo +//---------------------------------------------------------------------- +kern_return_t +MachTask::SaveExceptionPortInfo() +{ + return m_exc_port_info.Save(TaskPort()); +} + +//---------------------------------------------------------------------- +// MachTask::RestoreExceptionPortInfo +//---------------------------------------------------------------------- +kern_return_t +MachTask::RestoreExceptionPortInfo() +{ + return m_exc_port_info.Restore(TaskPort()); +} + + +//---------------------------------------------------------------------- +// MachTask::ReadMemory +//---------------------------------------------------------------------- +nub_size_t +MachTask::ReadMemory (nub_addr_t addr, nub_size_t size, void *buf) +{ + nub_size_t n = 0; + task_t task = TaskPort(); + if (task != TASK_NULL) + { + n = m_vm_memory.Read(task, addr, buf, size); + + DNBLogThreadedIf(LOG_MEMORY, "MachTask::ReadMemory ( addr = 0x%8.8llx, size = %zu, buf = %8.8p) => %u bytes read", (uint64_t)addr, size, buf, n); + if (DNBLogCheckLogBit(LOG_MEMORY_DATA_LONG) || (DNBLogCheckLogBit(LOG_MEMORY_DATA_SHORT) && size <= 8)) + { + DNBDataRef data((uint8_t*)buf, n, false); + data.Dump(0, n, addr, DNBDataRef::TypeUInt8, 16); + } + } + return n; +} + + +//---------------------------------------------------------------------- +// MachTask::WriteMemory +//---------------------------------------------------------------------- +nub_size_t +MachTask::WriteMemory (nub_addr_t addr, nub_size_t size, const void *buf) +{ + nub_size_t n = 0; + task_t task = TaskPort(); + if (task != TASK_NULL) + { + n = m_vm_memory.Write(task, addr, buf, size); + DNBLogThreadedIf(LOG_MEMORY, "MachTask::WriteMemory ( addr = 0x%8.8llx, size = %zu, buf = %8.8p) => %u bytes written", (uint64_t)addr, size, buf, n); + if (DNBLogCheckLogBit(LOG_MEMORY_DATA_LONG) || (DNBLogCheckLogBit(LOG_MEMORY_DATA_SHORT) && size <= 8)) + { + DNBDataRef data((uint8_t*)buf, n, false); + data.Dump(0, n, addr, DNBDataRef::TypeUInt8, 16); + } + } + return n; +} + +//---------------------------------------------------------------------- +// MachTask::TaskPortForProcessID +//---------------------------------------------------------------------- +task_t +MachTask::TaskPortForProcessID (DNBError &err) +{ + if (m_task == TASK_NULL && m_process != NULL) + m_task = MachTask::TaskPortForProcessID(m_process->ProcessID(), err); + return m_task; +} + +//---------------------------------------------------------------------- +// MachTask::TaskPortForProcessID +//---------------------------------------------------------------------- +task_t +MachTask::TaskPortForProcessID (pid_t pid, DNBError &err) +{ + task_t task = TASK_NULL; + if (pid != INVALID_NUB_PROCESS) + { + mach_port_t task_self = mach_task_self (); + err = ::task_for_pid ( task_self, pid, &task); + if (DNBLogCheckLogBit(LOG_TASK) || err.Fail()) + { + char str[1024]; + ::snprintf (str, + sizeof(str), + "::task_for_pid ( task_self, pid = %d, task => TASK_NULL (0x%4.4x) ) uid=%u, euid=%u gid=%u egid=%u (%s)", + pid, + task, + getuid(), + geteuid(), + getgid(), + getegid(), + err.AsString() ? err.AsString() : "success"); + if (err.Fail()) + err.SetErrorString(str); + err.LogThreaded(str); + } + } + return task; +} + + +//---------------------------------------------------------------------- +// MachTask::BasicInfo +//---------------------------------------------------------------------- +kern_return_t +MachTask::BasicInfo(struct task_basic_info *info) +{ + return BasicInfo (TaskPort(), info); +} + +//---------------------------------------------------------------------- +// MachTask::BasicInfo +//---------------------------------------------------------------------- +kern_return_t +MachTask::BasicInfo(task_t task, struct task_basic_info *info) +{ + if (info == NULL) + return KERN_INVALID_ARGUMENT; + + DNBError err; + mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT; + err = ::task_info (task, TASK_BASIC_INFO, (task_info_t)info, &count); + const bool log_process = DNBLogCheckLogBit(LOG_TASK); + if (log_process || err.Fail()) + err.LogThreaded("::task_info ( target_task = 0x%4.4x, flavor = TASK_BASIC_INFO, task_info_out => %p, task_info_outCnt => %u )", task, info, count); + if (DNBLogCheckLogBit(LOG_TASK) && DNBLogCheckLogBit(LOG_VERBOSE) && err.Success()) + { + float user = (float)info->user_time.seconds + (float)info->user_time.microseconds / 1000000.0f; + float system = (float)info->user_time.seconds + (float)info->user_time.microseconds / 1000000.0f; + DNBLogThreaded("task_basic_info = { suspend_count = %i, virtual_size = 0x%8.8x, resident_size = 0x%8.8x, user_time = %f, system_time = %f }", + info->suspend_count, info->virtual_size, info->resident_size, user, system); + } + return err.Error(); +} + + +//---------------------------------------------------------------------- +// MachTask::IsValid +// +// Returns true if a task is a valid task port for a current process. +//---------------------------------------------------------------------- +bool +MachTask::IsValid () const +{ + return MachTask::IsValid(TaskPort()); +} + +//---------------------------------------------------------------------- +// MachTask::IsValid +// +// Returns true if a task is a valid task port for a current process. +//---------------------------------------------------------------------- +bool +MachTask::IsValid (task_t task) +{ + if (task != TASK_NULL) + { + struct task_basic_info task_info; + return BasicInfo(task, &task_info) == KERN_SUCCESS; + } + return false; +} + + +bool +MachTask::StartExceptionThread(DNBError &err) +{ + DNBLogThreadedIf(LOG_EXCEPTIONS, "MachTask::%s ( )", __FUNCTION__); + task_t task = TaskPortForProcessID(err); + if (MachTask::IsValid(task)) + { + // Got the mach port for the current process + mach_port_t task_self = mach_task_self (); + + // Allocate an exception port that we will use to track our child process + err = ::mach_port_allocate (task_self, MACH_PORT_RIGHT_RECEIVE, &m_exception_port); + if (err.Fail()) + return false; + + // Add the ability to send messages on the new exception port + err = ::mach_port_insert_right (task_self, m_exception_port, m_exception_port, MACH_MSG_TYPE_MAKE_SEND); + if (err.Fail()) + return false; + + // Save the original state of the exception ports for our child process + SaveExceptionPortInfo(); + + // Set the ability to get all exceptions on this port + err = ::task_set_exception_ports (task, EXC_MASK_ALL, m_exception_port, EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, THREAD_STATE_NONE); + if (err.Fail()) + return false; + + // Create the exception thread + err = ::pthread_create (&m_exception_thread, NULL, MachTask::ExceptionThread, this); + return err.Success(); + } + else + { + DNBLogError("MachTask::%s (): task invalid, exception thread start failed.", __FUNCTION__); + } + return false; +} + +kern_return_t +MachTask::ShutDownExcecptionThread() +{ + DNBError err; + + err = RestoreExceptionPortInfo(); + + // NULL our our exception port and let our exception thread exit + mach_port_t exception_port = m_exception_port; + m_exception_port = NULL; + + err.SetError(::pthread_cancel(m_exception_thread), DNBError::POSIX); + if (DNBLogCheckLogBit(LOG_TASK) || err.Fail()) + err.LogThreaded("::pthread_cancel ( thread = %p )", m_exception_thread); + + err.SetError(::pthread_join(m_exception_thread, NULL), DNBError::POSIX); + if (DNBLogCheckLogBit(LOG_TASK) || err.Fail()) + err.LogThreaded("::pthread_join ( thread = %p, value_ptr = NULL)", m_exception_thread); + + // Deallocate our exception port that we used to track our child process + mach_port_t task_self = mach_task_self (); + err = ::mach_port_deallocate (task_self, exception_port); + if (DNBLogCheckLogBit(LOG_TASK) || err.Fail()) + err.LogThreaded("::mach_port_deallocate ( task = 0x%4.4x, name = 0x%4.4x )", task_self, exception_port); + exception_port = NULL; + + return err.Error(); +} + + +void * +MachTask::ExceptionThread (void *arg) +{ + if (arg == NULL) + return NULL; + + MachTask *mach_task = (MachTask*) arg; + MachProcess *mach_proc = mach_task->Process(); + DNBLogThreadedIf(LOG_EXCEPTIONS, "MachTask::%s ( arg = %p ) starting thread...", __FUNCTION__, arg); + + // We keep a count of the number of consecutive exceptions received so + // we know to grab all exceptions without a timeout. We do this to get a + // bunch of related exceptions on our exception port so we can process + // then together. When we have multiple threads, we can get an exception + // per thread and they will come in consecutively. The main loop in this + // thread can stop periodically if needed to service things related to this + // process. + // flag set in the options, so we will wait forever for an exception on + // our exception port. After we get one exception, we then will use the + // MACH_RCV_TIMEOUT option with a zero timeout to grab all other current + // exceptions for our process. After we have received the last pending + // exception, we will get a timeout which enables us to then notify + // our main thread that we have an exception bundle avaiable. We then wait + // for the main thread to tell this exception thread to start trying to get + // exceptions messages again and we start again with a mach_msg read with + // infinite timeout. + uint32_t num_exceptions_received = 0; + DNBError err; + task_t task = mach_task->TaskPort(); + mach_msg_timeout_t periodic_timeout = 0; + +#if defined (__arm__) + mach_msg_timeout_t watchdog_elapsed = 0; + mach_msg_timeout_t watchdog_timeout = 60 * 1000; + pid_t pid = mach_proc->ProcessID(); + CFReleaser<SBSWatchdogAssertionRef> watchdog; + + if (mach_proc->ProcessUsingSpringBoard()) + { + // Request a renewal for every 60 seconds if we attached using SpringBoard + watchdog.reset(::SBSWatchdogAssertionCreateForPID(NULL, pid, 60)); + DNBLogThreadedIf(LOG_TASK, "::SBSWatchdogAssertionCreateForPID (NULL, %4.4x, 60 ) => %p", pid, watchdog.get()); + + if (watchdog.get()) + { + ::SBSWatchdogAssertionRenew (watchdog.get()); + + CFTimeInterval watchdogRenewalInterval = ::SBSWatchdogAssertionGetRenewalInterval (watchdog.get()); + DNBLogThreadedIf(LOG_TASK, "::SBSWatchdogAssertionGetRenewalInterval ( %p ) => %g seconds", watchdog.get(), watchdogRenewalInterval); + if (watchdogRenewalInterval > 0.0) + { + watchdog_timeout = (mach_msg_timeout_t)watchdogRenewalInterval * 1000; + if (watchdog_timeout > 3000) + watchdog_timeout -= 1000; // Give us a second to renew our timeout + else if (watchdog_timeout > 1000) + watchdog_timeout -= 250; // Give us a quarter of a second to renew our timeout + } + } + if (periodic_timeout == 0 || periodic_timeout > watchdog_timeout) + periodic_timeout = watchdog_timeout; + } +#endif // #if defined (__arm__) + + while (mach_task->ExceptionPortIsValid()) + { + ::pthread_testcancel (); + + MachException::Message exception_message; + + + if (num_exceptions_received > 0) + { + // No timeout, just receive as many exceptions as we can since we already have one and we want + // to get all currently available exceptions for this task + err = exception_message.Receive(mach_task->ExceptionPort(), MACH_RCV_MSG | MACH_RCV_INTERRUPT | MACH_RCV_TIMEOUT, 0); + } + else if (periodic_timeout > 0) + { + // We need to stop periodically in this loop, so try and get a mach message with a valid timeout (ms) + err = exception_message.Receive(mach_task->ExceptionPort(), MACH_RCV_MSG | MACH_RCV_INTERRUPT | MACH_RCV_TIMEOUT, periodic_timeout); + } + else + { + // We don't need to parse all current exceptions or stop periodically, + // just wait for an exception forever. + err = exception_message.Receive(mach_task->ExceptionPort(), MACH_RCV_MSG | MACH_RCV_INTERRUPT, 0); + } + + if (err.Error() == MACH_RCV_INTERRUPTED) + { + // If we have no task port we should exit this thread + if (!mach_task->ExceptionPortIsValid()) + { + DNBLogThreadedIf(LOG_EXCEPTIONS, "thread cancelled..."); + break; + } + + // Make sure our task is still valid + if (MachTask::IsValid(task)) + { + // Task is still ok + DNBLogThreadedIf(LOG_EXCEPTIONS, "interrupted, but task still valid, continuing..."); + continue; + } + else + { + DNBLogThreadedIf(LOG_EXCEPTIONS, "task has exited..."); + mach_proc->SetState(eStateExited); + // Our task has died, exit the thread. + break; + } + } + else if (err.Error() == MACH_RCV_TIMED_OUT) + { + if (num_exceptions_received > 0) + { + // We were receiving all current exceptions with a timeout of zero + // it is time to go back to our normal looping mode + num_exceptions_received = 0; + + // Notify our main thread we have a complete exception message + // bundle available. + mach_proc->ExceptionMessageBundleComplete(); + + // in case we use a timeout value when getting exceptions... + // Make sure our task is still valid + if (MachTask::IsValid(task)) + { + // Task is still ok + DNBLogThreadedIf(LOG_EXCEPTIONS, "got a timeout, continuing..."); + continue; + } + else + { + DNBLogThreadedIf(LOG_EXCEPTIONS, "task has exited..."); + mach_proc->SetState(eStateExited); + // Our task has died, exit the thread. + break; + } + continue; + } + +#if defined (__arm__) + if (watchdog.get()) + { + watchdog_elapsed += periodic_timeout; + if (watchdog_elapsed >= watchdog_timeout) + { + DNBLogThreadedIf(LOG_TASK, "SBSWatchdogAssertionRenew ( %p )", watchdog.get()); + ::SBSWatchdogAssertionRenew (watchdog.get()); + watchdog_elapsed = 0; + } + } +#endif + } + else if (err.Error() != KERN_SUCCESS) + { + DNBLogThreadedIf(LOG_EXCEPTIONS, "got some other error, do something about it??? nah, continuing for now..."); + // TODO: notify of error? + } + else + { + if (exception_message.CatchExceptionRaise()) + { + ++num_exceptions_received; + mach_proc->ExceptionMessageReceived(exception_message); + } + } + } + +#if defined (__arm__) + if (watchdog.get()) + { + // TODO: change SBSWatchdogAssertionRelease to SBSWatchdogAssertionCancel when we + // all are up and running on systems that support it. The SBS framework has a #define + // that will forward SBSWatchdogAssertionRelease to SBSWatchdogAssertionCancel for now + // so it should still build either way. + DNBLogThreadedIf(LOG_TASK, "::SBSWatchdogAssertionRelease(%p)", watchdog.get()); + ::SBSWatchdogAssertionRelease (watchdog.get()); + } +#endif // #if defined (__arm__) + + DNBLogThreadedIf(LOG_EXCEPTIONS, "MachTask::%s (%p): thread exiting...", __FUNCTION__, arg); + return NULL; +} + + +// So the TASK_DYLD_INFO used to just return the address of the all image infos +// as a single member called "all_image_info". Then someone decided it would be +// a good idea to rename this first member to "all_image_info_addr" and add a +// size member called "all_image_info_size". This of course can not be detected +// using code or #defines. So to hack around this problem, we define our own +// version of the TASK_DYLD_INFO structure so we can guarantee what is inside it. + +struct hack_task_dyld_info { + mach_vm_address_t all_image_info_addr; + mach_vm_size_t all_image_info_size; +}; + +nub_addr_t +MachTask::GetDYLDAllImageInfosAddress (DNBError& err) +{ + struct hack_task_dyld_info dyld_info; + mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT; + // Make sure that COUNT isn't bigger than our hacked up struct hack_task_dyld_info. + // If it is, then make COUNT smaller to match. + if (count > (sizeof(struct hack_task_dyld_info) / sizeof(natural_t))) + count = (sizeof(struct hack_task_dyld_info) / sizeof(natural_t)); + + task_t task = TaskPortForProcessID (err); + if (err.Success()) + { + err = ::task_info (task, TASK_DYLD_INFO, (task_info_t)&dyld_info, &count); + if (err.Success()) + { + // We now have the address of the all image infos structure + return dyld_info.all_image_info_addr; + } + } + return INVALID_NUB_ADDRESS; +} + + +//---------------------------------------------------------------------- +// MachTask::AllocateMemory +//---------------------------------------------------------------------- +nub_addr_t +MachTask::AllocateMemory (size_t size, uint32_t permissions) +{ + mach_vm_address_t addr; + task_t task = TaskPort(); + if (task == TASK_NULL) + return INVALID_NUB_ADDRESS; + + DNBError err; + err = ::mach_vm_allocate (task, &addr, size, TRUE); + if (err.Error() == KERN_SUCCESS) + { + // Set the protections: + vm_prot_t mach_prot = 0; + if (permissions & eMemoryPermissionsReadable) + mach_prot |= VM_PROT_READ; + if (permissions & eMemoryPermissionsWritable) + mach_prot |= VM_PROT_WRITE; + if (permissions & eMemoryPermissionsExecutable) + mach_prot |= VM_PROT_EXECUTE; + + + err = ::mach_vm_protect (task, addr, size, 0, mach_prot); + if (err.Error() == KERN_SUCCESS) + { + m_allocations.insert (std::make_pair(addr, size)); + return addr; + } + ::mach_vm_deallocate (task, addr, size); + } + return INVALID_NUB_ADDRESS; +} + +//---------------------------------------------------------------------- +// MachTask::DeallocateMemory +//---------------------------------------------------------------------- +nub_bool_t +MachTask::DeallocateMemory (nub_addr_t addr) +{ + task_t task = TaskPort(); + if (task == TASK_NULL) + return false; + + // We have to stash away sizes for the allocations... + allocation_collection::iterator pos, end = m_allocations.end(); + for (pos = m_allocations.begin(); pos != end; pos++) + { + if ((*pos).first == addr) + { + m_allocations.erase(pos); + return ::mach_vm_deallocate (task, (*pos).first, (*pos).second) == KERN_SUCCESS; + } + + } + return false; +} + diff --git a/lldb/tools/debugserver/source/MacOSX/MachTask.h b/lldb/tools/debugserver/source/MacOSX/MachTask.h new file mode 100644 index 00000000000..3bf40e13457 --- /dev/null +++ b/lldb/tools/debugserver/source/MacOSX/MachTask.h @@ -0,0 +1,91 @@ +//===-- MachTask.h ----------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +//---------------------------------------------------------------------- +// +// MachTask.h +// debugserver +// +// Created by Greg Clayton on 12/5/08. +// +//===----------------------------------------------------------------------===// + +#ifndef __MachTask_h__ +#define __MachTask_h__ + +// C Includes +#include <mach/mach.h> +#include <sys/socket.h> +// C++ Includes +#include <map> +// Other libraries and framework includes +// Project includes +#include "MachException.h" +#include "MachVMMemory.h" +#include "PThreadMutex.h" + +class MachProcess; + +class MachTask +{ +public: + //------------------------------------------------------------------ + // Constructors and Destructors + //------------------------------------------------------------------ + MachTask (MachProcess *process); + virtual ~MachTask (); + + void Clear (); + + kern_return_t Suspend (); + kern_return_t Resume (); + + nub_size_t ReadMemory (nub_addr_t addr, nub_size_t size, void *buf); + nub_size_t WriteMemory (nub_addr_t addr, nub_size_t size, const void *buf); + + nub_addr_t AllocateMemory (nub_size_t size, uint32_t permissions); + nub_bool_t DeallocateMemory (nub_addr_t addr); + + mach_port_t ExceptionPort () const; + bool ExceptionPortIsValid () const; + kern_return_t SaveExceptionPortInfo (); + kern_return_t RestoreExceptionPortInfo (); + kern_return_t ShutDownExcecptionThread (); + + bool StartExceptionThread (DNBError &err); + nub_addr_t GetDYLDAllImageInfosAddress (DNBError& err); + kern_return_t BasicInfo (struct task_basic_info *info); + static kern_return_t BasicInfo (task_t task, struct task_basic_info *info); + bool IsValid () const; + static bool IsValid (task_t task); + static void * ExceptionThread (void *arg); + task_t TaskPort () const { return m_task; } + task_t TaskPortForProcessID (DNBError &err); + static task_t TaskPortForProcessID (pid_t pid, DNBError &err); + + MachProcess * Process () { return m_process; } + const MachProcess * Process () const { return m_process; } + +protected: + MachProcess * m_process; // The mach process that owns this MachTask + task_t m_task; + MachVMMemory m_vm_memory; // Special mach memory reading class that will take care of watching for page and region boundaries + MachException::PortInfo + m_exc_port_info; // Saved settings for all exception ports + pthread_t m_exception_thread; // Thread ID for the exception thread in case we need it + mach_port_t m_exception_port; // Exception port on which we will receive child exceptions + + typedef std::map <mach_vm_address_t, size_t> allocation_collection; + allocation_collection m_allocations; + +private: + MachTask(const MachTask&); // Outlaw + MachTask& operator=(const MachTask& rhs);// Outlaw +}; + +#endif // __MachTask_h__ diff --git a/lldb/tools/debugserver/source/MacOSX/MachThread.cpp b/lldb/tools/debugserver/source/MacOSX/MachThread.cpp new file mode 100644 index 00000000000..9d018f66ea6 --- /dev/null +++ b/lldb/tools/debugserver/source/MacOSX/MachThread.cpp @@ -0,0 +1,745 @@ +//===-- MachThread.cpp ------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/19/07. +// +//===----------------------------------------------------------------------===// + +#include "MachThread.h" +#include "MachProcess.h" +#include "DNBLog.h" +#include "DNB.h" + +static uint32_t +GetSequenceID() +{ + static uint32_t g_nextID = 0; + return ++g_nextID; +} + +MachThread::MachThread (MachProcess *process, thread_t thread) : + m_process(process), + m_tid(thread), + m_seq_id(GetSequenceID()), + m_state(eStateUnloaded), + m_state_mutex(PTHREAD_MUTEX_RECURSIVE), + m_breakID(INVALID_NUB_BREAK_ID), + m_suspendCount(0), + m_arch(this), + m_regSets() +{ + nub_size_t num_reg_sets = 0; + const DNBRegisterSetInfo *regSetInfo = m_arch.GetRegisterSetInfo(&num_reg_sets); + if (num_reg_sets > 0) + m_regSets.assign(regSetInfo, regSetInfo + num_reg_sets); + + ::memset (&m_basicInfo, 0, sizeof (m_basicInfo)); + DNBLogThreadedIf(LOG_THREAD | LOG_VERBOSE, "MachThread::MachThread ( process = %p, tid = 0x%4.4x, seq_id = %u )", &m_process, m_tid, m_seq_id); +} + +MachThread::~MachThread() +{ + DNBLogThreadedIf(LOG_THREAD | LOG_VERBOSE, "MachThread::~MachThread() for tid = 0x%4.4x (%u)", m_tid, m_seq_id); +} + + + +uint32_t +MachThread::Suspend() +{ + DNBLogThreadedIf(LOG_THREAD | LOG_VERBOSE, "MachThread::%s ( )", __FUNCTION__); + if (ThreadIDIsValid(m_tid)) + { + DNBError err(::thread_suspend (m_tid), DNBError::MachKernel); + if (err.Success()) + m_suspendCount++; + if (DNBLogCheckLogBit(LOG_THREAD) || err.Fail()) + err.LogThreaded("::thread_suspend (%4.4x)", m_tid); + } + return SuspendCount(); +} + +uint32_t +MachThread::Resume() +{ + DNBLogThreadedIf(LOG_THREAD | LOG_VERBOSE, "MachThread::%s ( )", __FUNCTION__); + if (ThreadIDIsValid(m_tid)) + { + while (m_suspendCount > 0) + { + DNBError err(::thread_resume (m_tid), DNBError::MachKernel); + if (err.Success()) + m_suspendCount--; + if (DNBLogCheckLogBit(LOG_THREAD) || err.Fail()) + err.LogThreaded("::thread_resume (%4.4x)", m_tid); + } + } + return SuspendCount(); +} + +bool +MachThread::RestoreSuspendCount() +{ + DNBLogThreadedIf(LOG_THREAD | LOG_VERBOSE, "MachThread::%s ( )", __FUNCTION__); + DNBError err; + if (ThreadIDIsValid(m_tid) == false) + return false; + else if (m_suspendCount > m_basicInfo.suspend_count) + { + while (m_suspendCount > m_basicInfo.suspend_count) + { + err = ::thread_resume (m_tid); + if (err.Success()) + --m_suspendCount; + if (DNBLogCheckLogBit(LOG_THREAD) || err.Fail()) + err.LogThreaded("::thread_resume (%4.4x)", m_tid); + } + } + else if (m_suspendCount < m_basicInfo.suspend_count) + { + while (m_suspendCount < m_basicInfo.suspend_count) + { + err = ::thread_suspend (m_tid); + if (err.Success()) + --m_suspendCount; + if (DNBLogCheckLogBit(LOG_THREAD) || err.Fail()) + err.LogThreaded("::thread_suspend (%4.4x)", m_tid); + } + } + return m_suspendCount == m_basicInfo.suspend_count; +} + + +const char * +MachThread::GetBasicInfoAsString () const +{ + static char g_basic_info_string[1024]; + struct thread_basic_info basicInfo; + + if (GetBasicInfo(m_tid, &basicInfo)) + { + +// char run_state_str[32]; +// size_t run_state_str_size = sizeof(run_state_str); +// switch (basicInfo.run_state) +// { +// case TH_STATE_RUNNING: strncpy(run_state_str, "running", run_state_str_size); break; +// case TH_STATE_STOPPED: strncpy(run_state_str, "stopped", run_state_str_size); break; +// case TH_STATE_WAITING: strncpy(run_state_str, "waiting", run_state_str_size); break; +// case TH_STATE_UNINTERRUPTIBLE: strncpy(run_state_str, "uninterruptible", run_state_str_size); break; +// case TH_STATE_HALTED: strncpy(run_state_str, "halted", run_state_str_size); break; +// default: snprintf(run_state_str, run_state_str_size, "%d", basicInfo.run_state); break; // ??? +// } + float user = (float)basicInfo.user_time.seconds + (float)basicInfo.user_time.microseconds / 1000000.0f; + float system = (float)basicInfo.user_time.seconds + (float)basicInfo.user_time.microseconds / 1000000.0f; + snprintf(g_basic_info_string, sizeof(g_basic_info_string), "Thread 0x%4.4x: user=%f system=%f cpu=%d sleep_time=%d", + InferiorThreadID(), + user, + system, + basicInfo.cpu_usage, + basicInfo.sleep_time); + + return g_basic_info_string; + } + return NULL; +} + +thread_t +MachThread::InferiorThreadID() const +{ + mach_msg_type_number_t i; + mach_port_name_array_t names; + mach_port_type_array_t types; + mach_msg_type_number_t ncount, tcount; + thread_t inferior_tid = INVALID_NUB_THREAD; + task_t my_task = ::mach_task_self(); + task_t task = m_process->Task().TaskPort(); + + kern_return_t kret = ::mach_port_names (task, &names, &ncount, &types, &tcount); + if (kret == KERN_SUCCESS) + { + + for (i = 0; i < ncount; i++) + { + mach_port_t my_name; + mach_msg_type_name_t my_type; + + kret = ::mach_port_extract_right (task, names[i], MACH_MSG_TYPE_COPY_SEND, &my_name, &my_type); + if (kret == KERN_SUCCESS) + { + ::mach_port_deallocate (my_task, my_name); + if (my_name == m_tid) + { + inferior_tid = names[i]; + break; + } + } + } + // Free up the names and types + ::vm_deallocate (my_task, (vm_address_t) names, ncount * sizeof (mach_port_name_t)); + ::vm_deallocate (my_task, (vm_address_t) types, tcount * sizeof (mach_port_type_t)); + } + return inferior_tid; +} + +bool +MachThread::GetBasicInfo(thread_t thread, struct thread_basic_info *basicInfoPtr) +{ + if (ThreadIDIsValid(thread)) + { + unsigned int info_count = THREAD_BASIC_INFO_COUNT; + kern_return_t err = ::thread_info (thread, THREAD_BASIC_INFO, (thread_info_t) basicInfoPtr, &info_count); + if (err == KERN_SUCCESS) + return true; + } + ::memset (basicInfoPtr, 0, sizeof (struct thread_basic_info)); + return false; +} + + +bool +MachThread::ThreadIDIsValid(thread_t thread) +{ + return thread != THREAD_NULL; +} + +bool +MachThread::GetRegisterState(int flavor, bool force) +{ + return m_arch.GetRegisterState(flavor, force) == KERN_SUCCESS; +} + +bool +MachThread::SetRegisterState(int flavor) +{ + return m_arch.SetRegisterState(flavor) == KERN_SUCCESS; +} + +uint64_t +MachThread::GetPC(uint64_t failValue) +{ + // Get program counter + return m_arch.GetPC(failValue); +} + +bool +MachThread::SetPC(uint64_t value) +{ + // Set program counter + return m_arch.SetPC(value); +} + +uint64_t +MachThread::GetSP(uint64_t failValue) +{ + // Get stack pointer + return m_arch.GetSP(failValue); +} + +nub_process_t +MachThread::ProcessID() const +{ + if (m_process) + return m_process->ProcessID(); + return INVALID_NUB_PROCESS; +} + +void +MachThread::Dump(uint32_t index) +{ + const char * thread_run_state = NULL; + + switch (m_basicInfo.run_state) + { + case TH_STATE_RUNNING: thread_run_state = "running"; break; // 1 thread is running normally + case TH_STATE_STOPPED: thread_run_state = "stopped"; break; // 2 thread is stopped + case TH_STATE_WAITING: thread_run_state = "waiting"; break; // 3 thread is waiting normally + case TH_STATE_UNINTERRUPTIBLE: thread_run_state = "uninter"; break; // 4 thread is in an uninterruptible wait + case TH_STATE_HALTED: thread_run_state = "halted "; break; // 5 thread is halted at a + default: thread_run_state = "???"; break; + } + + DNBLogThreaded("thread[%u] %4.4x (%u): pc: 0x%8.8llx sp: 0x%8.8llx breakID: %d user: %d.%06.6d system: %d.%06.6d cpu: %d policy: %d run_state: %d (%s) flags: %d suspend_count: %d (current %d) sleep_time: %d", + index, + m_tid, + m_seq_id, + GetPC(INVALID_NUB_ADDRESS), + GetSP(INVALID_NUB_ADDRESS), + m_breakID, + m_basicInfo.user_time.seconds, m_basicInfo.user_time.microseconds, + m_basicInfo.system_time.seconds, m_basicInfo.system_time.microseconds, + m_basicInfo.cpu_usage, + m_basicInfo.policy, + m_basicInfo.run_state, + thread_run_state, + m_basicInfo.flags, + m_basicInfo.suspend_count, m_suspendCount, + m_basicInfo.sleep_time); + //DumpRegisterState(0); +} + +void +MachThread::ThreadWillResume(const DNBThreadResumeAction *thread_action) +{ + if (thread_action->addr != INVALID_NUB_ADDRESS) + SetPC (thread_action->addr); + + SetState (thread_action->state); + switch (thread_action->state) + { + case eStateStopped: + case eStateSuspended: + Suspend(); + break; + + case eStateRunning: + case eStateStepping: + Resume(); + break; + } + m_arch.ThreadWillResume(); + m_stop_exception.Clear(); +} + +bool +MachThread::ShouldStop(bool &step_more) +{ + // See if this thread is at a breakpoint? + nub_break_t breakID = CurrentBreakpoint(); + + if (NUB_BREAK_ID_IS_VALID(breakID)) + { + // This thread is sitting at a breakpoint, ask the breakpoint + // if we should be stopping here. + if (Process()->Breakpoints().ShouldStop(ProcessID(), ThreadID(), breakID)) + return true; + else + { + // The breakpoint said we shouldn't stop, but we may have gotten + // a signal or the user may have requested to stop in some other + // way. Stop if we have a valid exception (this thread won't if + // another thread was the reason this process stopped) and that + // exception, is NOT a breakpoint exception (a common case would + // be a SIGINT signal). + if (GetStopException().IsValid() && !GetStopException().IsBreakpoint()) + return true; + } + } + else + { + if (m_arch.StepNotComplete()) + { + step_more = true; + return false; + } + // The thread state is used to let us know what the thread was + // trying to do. MachThread::ThreadWillResume() will set the + // thread state to various values depending if the thread was + // the current thread and if it was to be single stepped, or + // resumed. + if (GetState() == eStateRunning) + { + // If our state is running, then we should continue as we are in + // the process of stepping over a breakpoint. + return false; + } + else + { + // Stop if we have any kind of valid exception for this + // thread. + if (GetStopException().IsValid()) + return true; + } + } + return false; +} +bool +MachThread::IsStepping() +{ + // Return true if this thread is currently being stepped. + // MachThread::ThreadWillResume currently determines this by looking if we + // have been asked to single step, or if we are at a breakpoint instruction + // and have been asked to resume. In the latter case we need to disable the + // breakpoint we are at, single step, re-enable and continue. + nub_state_t state = GetState(); + return (state == eStateStepping) || + (state == eStateRunning && NUB_BREAK_ID_IS_VALID(CurrentBreakpoint())); +} + + +bool +MachThread::ThreadDidStop() +{ + // This thread has existed prior to resuming under debug nub control, + // and has just been stopped. Do any cleanup that needs to be done + // after running. + + // The thread state and breakpoint will still have the same values + // as they had prior to resuming the thread, so it makes it easy to check + // if we were trying to step a thread, or we tried to resume while being + // at a breakpoint. + + // When this method gets called, the process state is still in the + // state it was in while running so we can act accordingly. + m_arch.ThreadDidStop(); + + + // We may have suspended this thread so the primary thread could step + // without worrying about race conditions, so lets restore our suspend + // count. + RestoreSuspendCount(); + + // Update the basic information for a thread + MachThread::GetBasicInfo(m_tid, &m_basicInfo); + m_suspendCount = m_basicInfo.suspend_count; + + // See if we were at a breakpoint when we last resumed that we disabled, + // re-enable it. + nub_break_t breakID = CurrentBreakpoint(); + + if (NUB_BREAK_ID_IS_VALID(breakID)) + { + m_process->EnableBreakpoint(breakID); + if (m_suspendCount > 0) + { + SetState(eStateSuspended); + } + else + { + // If we last were at a breakpoint and we single stepped, our state + // will be "running" to indicate we need to continue after stepping + // over the breakpoint instruction. If we step over a breakpoint + // instruction, we need to stop. + if (GetState() == eStateRunning) + { + // Leave state set to running so we will continue automatically + // from this breakpoint + } + else + { + SetState(eStateStopped); + } + } + } + else + { + if (m_suspendCount > 0) + { + SetState(eStateSuspended); + } + else + { + SetState(eStateStopped); + } + } + + + SetCurrentBreakpoint(INVALID_NUB_BREAK_ID); + + return true; +} + +bool +MachThread::NotifyException(MachException::Data& exc) +{ + if (m_stop_exception.IsValid()) + { + // We may have more than one exception for a thread, but we need to + // only remember the one that we will say is the reason we stopped. + // We may have been single stepping and also gotten a signal exception, + // so just remember the most pertinent one. + if (m_stop_exception.IsBreakpoint()) + m_stop_exception = exc; + } + else + { + m_stop_exception = exc; + } + bool handled = m_arch.NotifyException(exc); + if (!handled) + { + handled = true; + nub_addr_t pc = GetPC(); + nub_break_t breakID = m_process->Breakpoints().FindIDByAddress(pc); + SetCurrentBreakpoint(breakID); + switch (exc.exc_type) + { + case EXC_BAD_ACCESS: + break; + case EXC_BAD_INSTRUCTION: + break; + case EXC_ARITHMETIC: + break; + case EXC_EMULATION: + break; + case EXC_SOFTWARE: + break; + case EXC_BREAKPOINT: + break; + case EXC_SYSCALL: + break; + case EXC_MACH_SYSCALL: + break; + case EXC_RPC_ALERT: + break; + } + } + return handled; +} + + +nub_state_t +MachThread::GetState() +{ + // If any other threads access this we will need a mutex for it + PTHREAD_MUTEX_LOCKER (locker, m_state_mutex); + return m_state; +} + +void +MachThread::SetState(nub_state_t state) +{ + PTHREAD_MUTEX_LOCKER (locker, m_state_mutex); + m_state = state; + DNBLogThreadedIf(LOG_THREAD, "MachThread::SetState ( %s ) for tid = 0x%4.4x", DNBStateAsString(state), m_tid); +} + +uint32_t +MachThread::GetNumRegistersInSet(int regSet) const +{ + if (regSet < m_regSets.size()) + return m_regSets[regSet].num_registers; + return 0; +} + +const char * +MachThread::GetRegisterSetName(int regSet) const +{ + if (regSet < m_regSets.size()) + return m_regSets[regSet].name; + return NULL; +} + +const DNBRegisterInfo * +MachThread::GetRegisterInfo(int regSet, int regIndex) const +{ + if (regSet < m_regSets.size()) + if (regIndex < m_regSets[regSet].num_registers) + return &m_regSets[regSet].registers[regIndex]; + return NULL; +} +void +MachThread::DumpRegisterState(int regSet) +{ + if (regSet == REGISTER_SET_ALL) + { + for (regSet = 1; regSet < m_regSets.size(); regSet++) + DumpRegisterState(regSet); + } + else + { + if (m_arch.RegisterSetStateIsValid(regSet)) + { + const size_t numRegisters = GetNumRegistersInSet(regSet); + size_t regIndex = 0; + DNBRegisterValueClass reg; + for (regIndex = 0; regIndex < numRegisters; ++regIndex) + { + if (m_arch.GetRegisterValue(regSet, regIndex, ®)) + { + reg.Dump(NULL, NULL); + } + } + } + else + { + DNBLog("%s: registers are not currently valid.", GetRegisterSetName(regSet)); + } + } +} + +const DNBRegisterSetInfo * +MachThread::GetRegisterSetInfo(nub_size_t *num_reg_sets ) const +{ + *num_reg_sets = m_regSets.size(); + return &m_regSets[0]; +} + +bool +MachThread::GetRegisterValue ( uint32_t set, uint32_t reg, DNBRegisterValue *value ) +{ + return m_arch.GetRegisterValue(set, reg, value); +} + +bool +MachThread::SetRegisterValue ( uint32_t set, uint32_t reg, const DNBRegisterValue *value ) +{ + return m_arch.SetRegisterValue(set, reg, value); +} + +nub_size_t +MachThread::GetRegisterContext (void *buf, nub_size_t buf_len) +{ + return m_arch.GetRegisterContext(buf, buf_len); +} + +nub_size_t +MachThread::SetRegisterContext (const void *buf, nub_size_t buf_len) +{ + return m_arch.SetRegisterContext(buf, buf_len); +} + +uint32_t +MachThread::EnableHardwareBreakpoint (const DNBBreakpoint *bp) +{ + if (bp != NULL && bp->IsBreakpoint()) + return m_arch.EnableHardwareBreakpoint(bp->Address(), bp->ByteSize()); + return INVALID_NUB_HW_INDEX; +} + +uint32_t +MachThread::EnableHardwareWatchpoint (const DNBBreakpoint *wp) +{ + if (wp != NULL && wp->IsWatchpoint()) + return m_arch.EnableHardwareWatchpoint(wp->Address(), wp->ByteSize(), wp->WatchpointRead(), wp->WatchpointWrite()); + return INVALID_NUB_HW_INDEX; +} + +bool +MachThread::DisableHardwareBreakpoint (const DNBBreakpoint *bp) +{ + if (bp != NULL && bp->IsHardware()) + return m_arch.DisableHardwareBreakpoint(bp->GetHardwareIndex()); + return false; +} + +bool +MachThread::DisableHardwareWatchpoint (const DNBBreakpoint *wp) +{ + if (wp != NULL && wp->IsHardware()) + return m_arch.DisableHardwareWatchpoint(wp->GetHardwareIndex()); + return false; +} + + +void +MachThread::NotifyBreakpointChanged (const DNBBreakpoint *bp) +{ + nub_break_t breakID = bp->GetID(); + if (bp->IsEnabled()) + { + if (bp->Address() == GetPC()) + { + SetCurrentBreakpoint(breakID); + } + } + else + { + if (CurrentBreakpoint() == breakID) + { + SetCurrentBreakpoint(INVALID_NUB_BREAK_ID); + } + } +} + +bool +MachThread::GetIdentifierInfo () +{ +#ifdef THREAD_IDENTIFIER_INFO_COUNT + if (m_ident_info.thread_id == 0) + { + mach_msg_type_number_t count = THREAD_IDENTIFIER_INFO_COUNT; + return ::thread_info (ThreadID(), THREAD_IDENTIFIER_INFO, (thread_info_t) &m_ident_info, &count) == KERN_SUCCESS; + } +#endif + + return false; +} + + +const char * +MachThread::GetName () +{ + if (GetIdentifierInfo ()) + { + int len = ::proc_pidinfo (m_process->ProcessID(), PROC_PIDTHREADINFO, m_ident_info.thread_handle, &m_proc_threadinfo, sizeof (m_proc_threadinfo)); + + if (len && m_proc_threadinfo.pth_name[0]) + return m_proc_threadinfo.pth_name; + } + return NULL; +} + + +// +//const char * +//MachThread::GetDispatchQueueName() +//{ +// if (GetIdentifierInfo ()) +// { +// if (m_ident_info.dispatch_qaddr == 0) +// return NULL; +// +// uint8_t memory_buffer[8]; +// DNBDataRef data(memory_buffer, sizeof(memory_buffer), false); +// ModuleSP module_sp(GetProcess()->GetTarget().GetImages().FindFirstModuleForFileSpec (FileSpec("libSystem.B.dylib"))); +// if (module_sp.get() == NULL) +// return NULL; +// +// lldb::addr_t dispatch_queue_offsets_addr = LLDB_INVALID_ADDRESS; +// const Symbol *dispatch_queue_offsets_symbol = module_sp->FindFirstSymbolWithNameAndType (ConstString("dispatch_queue_offsets"), eSymbolTypeData); +// if (dispatch_queue_offsets_symbol) +// dispatch_queue_offsets_addr = dispatch_queue_offsets_symbol->GetValue().GetLoadAddress(GetProcess()); +// +// if (dispatch_queue_offsets_addr == LLDB_INVALID_ADDRESS) +// return NULL; +// +// // Excerpt from src/queue_private.h +// struct dispatch_queue_offsets_s +// { +// uint16_t dqo_version; +// uint16_t dqo_label; +// uint16_t dqo_label_size; +// } dispatch_queue_offsets; +// +// +// if (GetProcess()->ReadMemory (dispatch_queue_offsets_addr, memory_buffer, sizeof(dispatch_queue_offsets)) == sizeof(dispatch_queue_offsets)) +// { +// uint32_t data_offset = 0; +// if (data.GetU16(&data_offset, &dispatch_queue_offsets.dqo_version, sizeof(dispatch_queue_offsets)/sizeof(uint16_t))) +// { +// if (GetProcess()->ReadMemory (m_ident_info.dispatch_qaddr, &memory_buffer, data.GetAddressByteSize()) == data.GetAddressByteSize()) +// { +// data_offset = 0; +// lldb::addr_t queue_addr = data.GetAddress(&data_offset); +// lldb::addr_t label_addr = queue_addr + dispatch_queue_offsets.dqo_label; +// const size_t chunk_size = 32; +// uint32_t label_pos = 0; +// m_dispatch_queue_name.resize(chunk_size, '\0'); +// while (1) +// { +// size_t bytes_read = GetProcess()->ReadMemory (label_addr + label_pos, &m_dispatch_queue_name[label_pos], chunk_size); +// +// if (bytes_read <= 0) +// break; +// +// if (m_dispatch_queue_name.find('\0', label_pos) != std::string::npos) +// break; +// label_pos += bytes_read; +// } +// m_dispatch_queue_name.erase(m_dispatch_queue_name.find('\0')); +// } +// } +// } +// } +// +// if (m_dispatch_queue_name.empty()) +// return NULL; +// return m_dispatch_queue_name.c_str(); +//} diff --git a/lldb/tools/debugserver/source/MacOSX/MachThread.h b/lldb/tools/debugserver/source/MacOSX/MachThread.h new file mode 100644 index 00000000000..2bf5ff2c3bb --- /dev/null +++ b/lldb/tools/debugserver/source/MacOSX/MachThread.h @@ -0,0 +1,124 @@ +//===-- MachThread.h --------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/19/07. +// +//===----------------------------------------------------------------------===// + +#ifndef __MachThread_h__ +#define __MachThread_h__ + +#include <string> +#include <vector> +#include <tr1/memory> // for std::tr1::shared_ptr + +#include <libproc.h> +#include <mach/mach.h> +#include <pthread.h> +#include <sys/signal.h> + +#include "PThreadCondition.h" +#include "PThreadMutex.h" +#include "MachException.h" +#include "DNBArch.h" +#include "DNBRegisterInfo.h" + +class DNBBreakpoint; +class MachProcess; + +class MachThread +{ +public: + + MachThread (MachProcess *process, thread_t thread = 0); + ~MachThread (); + + MachProcess * Process() { return m_process; } + const MachProcess * + Process() const { return m_process; } + nub_process_t ProcessID() const; + void Dump(uint32_t index); + thread_t ThreadID() const { return m_tid; } + thread_t InferiorThreadID() const; + + uint32_t SequenceID() const { return m_seq_id; } + static bool ThreadIDIsValid(thread_t thread); + uint32_t Resume(); + uint32_t Suspend(); + uint32_t SuspendCount() const { return m_suspendCount; } + bool RestoreSuspendCount(); + + bool GetRegisterState(int flavor, bool force); + bool SetRegisterState(int flavor); + uint64_t GetPC(uint64_t failValue = INVALID_NUB_ADDRESS); // Get program counter + bool SetPC(uint64_t value); // Set program counter + uint64_t GetSP(uint64_t failValue = INVALID_NUB_ADDRESS); // Get stack pointer + + nub_break_t CurrentBreakpoint() const { return m_breakID; } + void SetCurrentBreakpoint(nub_break_t breakID) { m_breakID = breakID; } + uint32_t EnableHardwareBreakpoint (const DNBBreakpoint *breakpoint); + uint32_t EnableHardwareWatchpoint (const DNBBreakpoint *watchpoint); + bool DisableHardwareBreakpoint (const DNBBreakpoint *breakpoint); + bool DisableHardwareWatchpoint (const DNBBreakpoint *watchpoint); + + nub_state_t GetState(); + void SetState(nub_state_t state); + + void ThreadWillResume (const DNBThreadResumeAction *thread_action); + bool ShouldStop(bool &step_more); + bool IsStepping(); + bool ThreadDidStop(); + bool NotifyException(MachException::Data& exc); + const MachException::Data& GetStopException() { return m_stop_exception; } + + uint32_t GetNumRegistersInSet(int regSet) const; + const char * GetRegisterSetName(int regSet) const; + const DNBRegisterInfo * + GetRegisterInfo(int regSet, int regIndex) const; + void DumpRegisterState(int regSet); + const DNBRegisterSetInfo * + GetRegisterSetInfo(nub_size_t *num_reg_sets ) const; + bool GetRegisterValue ( uint32_t reg_set_idx, uint32_t reg_idx, DNBRegisterValue *reg_value ); + bool SetRegisterValue ( uint32_t reg_set_idx, uint32_t reg_idx, const DNBRegisterValue *reg_value ); + nub_size_t GetRegisterContext (void *buf, nub_size_t buf_len); + nub_size_t SetRegisterContext (const void *buf, nub_size_t buf_len); + void NotifyBreakpointChanged (const DNBBreakpoint *bp); + const char * GetBasicInfoAsString () const; + const char * GetName (); +protected: + static bool GetBasicInfo(thread_t threadID, struct thread_basic_info *basic_info); + + bool + GetIdentifierInfo (); + +// const char * +// GetDispatchQueueName(); +// + MachProcess * m_process; // The process that owns this thread + thread_t m_tid; // The thread port for this thread + uint32_t m_seq_id; // A Sequential ID that increments with each new thread + nub_state_t m_state; // The state of our process + PThreadMutex m_state_mutex; // Multithreaded protection for m_state + nub_break_t m_breakID; // Breakpoint that this thread is (stopped)/was(running) at (NULL for none) + struct thread_basic_info m_basicInfo; // Basic information for a thread used to see if a thread is valid + uint32_t m_suspendCount; // The current suspend count + MachException::Data m_stop_exception; // The best exception that describes why this thread is stopped + DNBArch m_arch; // Arch specific information for register state and more + std::vector<DNBRegisterSetInfo> m_regSets; // Register set information for this thread +#ifdef THREAD_IDENTIFIER_INFO_COUNT + thread_identifier_info_data_t m_ident_info; + struct proc_threadinfo m_proc_threadinfo; + std::string m_dispatch_queue_name; +#endif + +}; + +typedef std::tr1::shared_ptr<MachThread> MachThreadSP; + +#endif diff --git a/lldb/tools/debugserver/source/MacOSX/MachThreadList.cpp b/lldb/tools/debugserver/source/MacOSX/MachThreadList.cpp new file mode 100644 index 00000000000..b1ccc74f8e6 --- /dev/null +++ b/lldb/tools/debugserver/source/MacOSX/MachThreadList.cpp @@ -0,0 +1,432 @@ +//===-- MachThreadList.cpp --------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/19/07. +// +//===----------------------------------------------------------------------===// + +#include "MachThreadList.h" +#include "DNBLog.h" +#include "DNBThreadResumeActions.h" +#include "MachProcess.h" + +MachThreadList::MachThreadList() : + m_threads(), + m_threads_mutex(PTHREAD_MUTEX_RECURSIVE) +{ +} + +MachThreadList::~MachThreadList() +{ +} + +// Not thread safe, must lock m_threads_mutex prior to using this function. +uint32_t +MachThreadList::GetThreadIndexByID(thread_t tid) const +{ + uint32_t idx = 0; + const uint32_t num_threads = m_threads.size(); + for (idx = 0; idx < num_threads; ++idx) + { + if (m_threads[idx]->ThreadID() == tid) + return idx; + } + return ~((uint32_t)0); +} + +nub_state_t +MachThreadList::GetState(thread_t tid) +{ + uint32_t idx = GetThreadIndexByID(tid); + if (idx < m_threads.size()) + return m_threads[idx]->GetState(); + return eStateInvalid; +} + +const char * +MachThreadList::GetName (thread_t tid) +{ + uint32_t idx = GetThreadIndexByID(tid); + if (idx < m_threads.size()) + return m_threads[idx]->GetName(); + return NULL; +} + +nub_thread_t +MachThreadList::SetCurrentThread(thread_t tid) +{ + uint32_t idx = GetThreadIndexByID(tid); + if (idx < m_threads.size()) + m_current_thread = m_threads[idx]; + + if (m_current_thread.get()) + return m_current_thread->ThreadID(); + return INVALID_NUB_THREAD; +} + + +bool +MachThreadList::GetThreadStoppedReason(nub_thread_t tid, struct DNBThreadStopInfo *stop_info) const +{ + uint32_t idx = GetThreadIndexByID(tid); + if (idx < m_threads.size()) + return m_threads[idx]->GetStopException().GetStopInfo(stop_info); + return false; +} + +bool +MachThreadList::GetIdentifierInfo (nub_thread_t tid, thread_identifier_info_data_t *ident_info) +{ + mach_msg_type_number_t count = THREAD_IDENTIFIER_INFO_COUNT; + return ::thread_info (tid, THREAD_IDENTIFIER_INFO, (thread_info_t)ident_info, &count) == KERN_SUCCESS; +} + +void +MachThreadList::DumpThreadStoppedReason(nub_thread_t tid) const +{ + uint32_t idx = GetThreadIndexByID(tid); + if (idx < m_threads.size()) + m_threads[idx]->GetStopException().DumpStopReason(); +} + +const char * +MachThreadList::GetThreadInfo(nub_thread_t tid) const +{ + uint32_t idx = GetThreadIndexByID(tid); + if (idx < m_threads.size()) + return m_threads[idx]->GetBasicInfoAsString(); + return NULL; +} + +bool +MachThreadList::GetRegisterValue ( nub_thread_t tid, uint32_t reg_set_idx, uint32_t reg_idx, DNBRegisterValue *reg_value ) const +{ + uint32_t idx = GetThreadIndexByID(tid); + if (idx < m_threads.size()) + return m_threads[idx]->GetRegisterValue(reg_set_idx, reg_idx, reg_value); + + return false; +} + +bool +MachThreadList::SetRegisterValue ( nub_thread_t tid, uint32_t reg_set_idx, uint32_t reg_idx, const DNBRegisterValue *reg_value ) const +{ + uint32_t idx = GetThreadIndexByID(tid); + if (idx < m_threads.size()) + return m_threads[idx]->SetRegisterValue(reg_set_idx, reg_idx, reg_value); + + return false; +} + +nub_size_t +MachThreadList::GetRegisterContext (nub_thread_t tid, void *buf, size_t buf_len) +{ + uint32_t idx = GetThreadIndexByID(tid); + if (idx < m_threads.size()) + return m_threads[idx]->GetRegisterContext (buf, buf_len); + return 0; +} + +nub_size_t +MachThreadList::SetRegisterContext (nub_thread_t tid, const void *buf, size_t buf_len) +{ + uint32_t idx = GetThreadIndexByID(tid); + if (idx < m_threads.size()) + return m_threads[idx]->SetRegisterContext (buf, buf_len); + return 0; +} + +nub_size_t +MachThreadList::NumThreads() const +{ + return m_threads.size(); +} + +nub_thread_t +MachThreadList::ThreadIDAtIndex(nub_size_t idx) const +{ + if (idx < m_threads.size()) + return m_threads[idx]->ThreadID(); + return INVALID_NUB_THREAD; +} + +nub_thread_t +MachThreadList::CurrentThreadID ( ) +{ + MachThreadSP threadSP; + CurrentThread(threadSP); + if (threadSP.get()) + return threadSP->ThreadID(); + return INVALID_NUB_THREAD; +} + +bool +MachThreadList::NotifyException(MachException::Data& exc) +{ + uint32_t idx = GetThreadIndexByID(exc.thread_port); + if (idx < m_threads.size()) + { + m_threads[idx]->NotifyException(exc); + return true; + } + return false; +} + +/* +MachThreadList::const_iterator +MachThreadList::FindThreadByID(thread_t tid) const +{ + const_iterator pos; + const_iterator end = m_threads.end(); + for (pos = m_threads.begin(); pos != end; ++pos) + { + if (pos->ThreadID() == tid) + return pos; + } + return NULL; +} +*/ +void +MachThreadList::Clear() +{ + m_threads.clear(); +} + +uint32_t +MachThreadList::UpdateThreadList(MachProcess *process, bool update) +{ + // locker will keep a mutex locked until it goes out of scope + DNBLogThreadedIf(LOG_THREAD | LOG_VERBOSE, "MachThreadList::UpdateThreadList (pid = %4.4x, update = %u )", process->ProcessID(), update); + PTHREAD_MUTEX_LOCKER (locker, m_threads_mutex); + + if (m_threads.empty() || update) + { + thread_array_t thread_list = NULL; + mach_msg_type_number_t thread_list_count = 0; + task_t task = process->Task().TaskPort(); + DNBError err(::task_threads (task, &thread_list, &thread_list_count), DNBError::MachKernel); + + if (DNBLogCheckLogBit(LOG_THREAD) || err.Fail()) + err.LogThreaded("::task_threads ( task = 0x%4.4x, thread_list => %p, thread_list_count => %u )", task, thread_list, thread_list_count); + + if (err.Error() == KERN_SUCCESS && thread_list_count > 0) + { + MachThreadList::collection currThreads; + const size_t numOldThreads = m_threads.size(); + size_t idx; + // Iterator through the current thread list and see which threads + // we already have in our list (keep them), which ones we don't + // (add them), and which ones are not around anymore (remove them). + for (idx = 0; idx < thread_list_count; ++idx) + { + uint32_t existing_idx = 0; + if (numOldThreads > 0) + existing_idx = GetThreadIndexByID(thread_list[idx]); + if (existing_idx < numOldThreads) + { + // Keep the existing thread class + currThreads.push_back(m_threads[existing_idx]); + } + else + { + // We don't have this thread, lets add it. + MachThreadSP threadSP(new MachThread(process, thread_list[idx])); + currThreads.push_back(threadSP); + } + } + + m_threads.swap(currThreads); + m_current_thread.reset(); + + // Free the vm memory given to us by ::task_threads() + vm_size_t thread_list_size = (vm_size_t) (thread_list_count * sizeof (thread_t)); + ::vm_deallocate (::mach_task_self(), + (vm_address_t)thread_list, + thread_list_size); + } + } + return m_threads.size(); +} + + +void +MachThreadList::CurrentThread(MachThreadSP& threadSP) +{ + // locker will keep a mutex locked until it goes out of scope + PTHREAD_MUTEX_LOCKER (locker, m_threads_mutex); + if (m_current_thread.get() == NULL) + { + // Figure out which thread is going to be our current thread. + // This is currently done by finding the first thread in the list + // that has a valid exception. + const size_t num_threads = m_threads.size(); + size_t idx; + for (idx = 0; idx < num_threads; ++idx) + { + MachThread *thread = m_threads[idx].get(); + if (thread->GetStopException().IsValid()) + { + m_current_thread = m_threads[idx]; + break; + } + } + } + threadSP = m_current_thread; +} + +void +MachThreadList::GetRegisterState(int flavor, bool force) +{ + uint32_t idx = 0; + const uint32_t num_threads = m_threads.size(); + for (idx = 0; idx < num_threads; ++idx) + { + m_threads[idx]->GetRegisterState(flavor, force); + } +} + +void +MachThreadList::SetRegisterState(int flavor) +{ + uint32_t idx = 0; + const uint32_t num_threads = m_threads.size(); + for (idx = 0; idx < num_threads; ++idx) + { + m_threads[idx]->SetRegisterState(flavor); + } +} + +void +MachThreadList::Dump() const +{ + uint32_t idx = 0; + const uint32_t num_threads = m_threads.size(); + for (idx = 0; idx < num_threads; ++idx) + { + m_threads[idx]->Dump(idx); + } +} + + +void +MachThreadList::ProcessWillResume(MachProcess *process, const DNBThreadResumeActions &thread_actions) +{ + uint32_t idx = 0; + const uint32_t num_threads = m_threads.size(); + + for (idx = 0; idx < num_threads; ++idx) + { + MachThread *thread = m_threads[idx].get(); + + const DNBThreadResumeAction *thread_action = thread_actions.GetActionForThread (thread->ThreadID(), true); + // There must always be a thread action for every thread. + assert (thread_action); + thread->ThreadWillResume (thread_action); + } +} + +uint32_t +MachThreadList::ProcessDidStop(MachProcess *process) +{ + // Update our thread list + const uint32_t num_threads = UpdateThreadList(process, true); + uint32_t idx = 0; + for (idx = 0; idx < num_threads; ++idx) + { + m_threads[idx]->ThreadDidStop(); + } + return num_threads; +} + +//---------------------------------------------------------------------- +// Check each thread in our thread list to see if we should notify our +// client of the current halt in execution. +// +// Breakpoints can have callback functions associated with them than +// can return true to stop, or false to continue executing the inferior. +// +// RETURNS +// true if we should stop and notify our clients +// false if we should resume our child process and skip notification +//---------------------------------------------------------------------- +bool +MachThreadList::ShouldStop(bool &step_more) +{ + uint32_t should_stop = false; + const uint32_t num_threads = m_threads.size(); + uint32_t idx = 0; + for (idx = 0; !should_stop && idx < num_threads; ++idx) + { + should_stop = m_threads[idx]->ShouldStop(step_more); + } + return should_stop; +} + + +void +MachThreadList::NotifyBreakpointChanged (const DNBBreakpoint *bp) +{ + uint32_t idx = 0; + const uint32_t num_threads = m_threads.size(); + for (idx = 0; idx < num_threads; ++idx) + { + m_threads[idx]->NotifyBreakpointChanged(bp); + } +} + + +uint32_t +MachThreadList::EnableHardwareBreakpoint (const DNBBreakpoint* bp) const +{ + if (bp != NULL) + { + uint32_t idx = GetThreadIndexByID(bp->ThreadID()); + if (idx < m_threads.size()) + return m_threads[idx]->EnableHardwareBreakpoint(bp); + } + return INVALID_NUB_HW_INDEX; +} + +bool +MachThreadList::DisableHardwareBreakpoint (const DNBBreakpoint* bp) const +{ + if (bp != NULL) + { + uint32_t idx = GetThreadIndexByID(bp->ThreadID()); + if (idx < m_threads.size()) + return m_threads[idx]->DisableHardwareBreakpoint(bp); + } + return false; +} + +uint32_t +MachThreadList::EnableHardwareWatchpoint (const DNBBreakpoint* wp) const +{ + if (wp != NULL) + { + uint32_t idx = GetThreadIndexByID(wp->ThreadID()); + if (idx < m_threads.size()) + return m_threads[idx]->EnableHardwareWatchpoint(wp); + } + return INVALID_NUB_HW_INDEX; +} + +bool +MachThreadList::DisableHardwareWatchpoint (const DNBBreakpoint* wp) const +{ + if (wp != NULL) + { + uint32_t idx = GetThreadIndexByID(wp->ThreadID()); + if (idx < m_threads.size()) + return m_threads[idx]->DisableHardwareWatchpoint(wp); + } + return false; +} + + diff --git a/lldb/tools/debugserver/source/MacOSX/MachThreadList.h b/lldb/tools/debugserver/source/MacOSX/MachThreadList.h new file mode 100644 index 00000000000..b52a97b547b --- /dev/null +++ b/lldb/tools/debugserver/source/MacOSX/MachThreadList.h @@ -0,0 +1,71 @@ +//===-- MachThreadList.h ----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/19/07. +// +//===----------------------------------------------------------------------===// + +#ifndef __MachThreadList_h__ +#define __MachThreadList_h__ + +#include "MachThread.h" + +class DNBThreadResumeActions; + +class MachThreadList +{ +public: + MachThreadList (); + ~MachThreadList (); + + void Clear (); + void Dump () const; + void GetRegisterState (int flavor, bool force); + void SetRegisterState (int flavor); + bool GetRegisterValue (nub_thread_t tid, uint32_t reg_set_idx, uint32_t reg_idx, DNBRegisterValue *reg_value) const; + bool SetRegisterValue (nub_thread_t tid, uint32_t reg_set_idx, uint32_t reg_idx, const DNBRegisterValue *reg_value) const; + nub_size_t GetRegisterContext (nub_thread_t tid, void *buf, size_t buf_len); + nub_size_t SetRegisterContext (nub_thread_t tid, const void *buf, size_t buf_len); + const char * GetThreadInfo (nub_thread_t tid) const; + void ProcessWillResume (MachProcess *process, const DNBThreadResumeActions &thread_actions); + uint32_t ProcessDidStop (MachProcess *process); + bool NotifyException (MachException::Data& exc); + bool ShouldStop (bool &step_more); + const char * GetName (thread_t tid); + nub_state_t GetState (thread_t tid); + nub_thread_t SetCurrentThread (thread_t tid); + bool GetThreadStoppedReason (nub_thread_t tid, struct DNBThreadStopInfo *stop_info) const; + void DumpThreadStoppedReason (nub_thread_t tid) const; + bool GetIdentifierInfo (nub_thread_t tid, thread_identifier_info_data_t *ident_info); + nub_size_t NumThreads () const; + nub_thread_t ThreadIDAtIndex (nub_size_t idx) const; + nub_thread_t CurrentThreadID (); + uint32_t GetThreadIndexByID (thread_t tid) const; + void CurrentThread (MachThreadSP& threadSP); + void NotifyBreakpointChanged (const DNBBreakpoint *bp); + uint32_t EnableHardwareBreakpoint (const DNBBreakpoint *bp) const; + bool DisableHardwareBreakpoint (const DNBBreakpoint *bp) const; + uint32_t EnableHardwareWatchpoint (const DNBBreakpoint *wp) const; + bool DisableHardwareWatchpoint (const DNBBreakpoint *wp) const; + +protected: + typedef std::vector<MachThreadSP> collection; + typedef collection::iterator iterator; + typedef collection::const_iterator const_iterator; + + uint32_t UpdateThreadList (MachProcess *process, bool update); +// const_iterator FindThreadByID (thread_t tid) const; + + collection m_threads; + PThreadMutex m_threads_mutex; + MachThreadSP m_current_thread; +}; + +#endif // #ifndef __MachThreadList_h__ + diff --git a/lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp b/lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp new file mode 100644 index 00000000000..eb7e10746e2 --- /dev/null +++ b/lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp @@ -0,0 +1,186 @@ +//===-- MachVMMemory.cpp ----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/26/07. +// +//===----------------------------------------------------------------------===// + +#include "MachVMMemory.h" +#include "MachVMRegion.h" +#include "DNBLog.h" +#include <mach/mach_vm.h> + +MachVMMemory::MachVMMemory() : + m_page_size (kInvalidPageSize), + m_err (0) +{ +} + +MachVMMemory::~MachVMMemory() +{ +} + +nub_size_t +MachVMMemory::PageSize() +{ + if (m_page_size == kInvalidPageSize) + { + m_err = ::host_page_size( ::mach_host_self(), &m_page_size); + if (m_err.Fail()) + m_page_size = 0; + } + return m_page_size; +} + +nub_size_t +MachVMMemory::MaxBytesLeftInPage(nub_addr_t addr, nub_size_t count) +{ + const nub_size_t page_size = PageSize(); + if (page_size > 0) + { + nub_size_t page_offset = (addr % page_size); + nub_size_t bytes_left_in_page = page_size - page_offset; + if (count > bytes_left_in_page) + count = bytes_left_in_page; + } + return count; +} + +nub_size_t +MachVMMemory::Read(task_t task, nub_addr_t address, void *data, nub_size_t data_count) +{ + if (data == NULL || data_count == 0) + return 0; + + nub_size_t total_bytes_read = 0; + nub_addr_t curr_addr = address; + uint8_t *curr_data = (uint8_t*)data; + while (total_bytes_read < data_count) + { + mach_vm_size_t curr_size = MaxBytesLeftInPage(curr_addr, data_count - total_bytes_read); + mach_msg_type_number_t curr_bytes_read = 0; + vm_offset_t vm_memory = NULL; + m_err = ::mach_vm_read (task, curr_addr, curr_size, &vm_memory, &curr_bytes_read); + if (DNBLogCheckLogBit(LOG_MEMORY) || m_err.Fail()) + m_err.LogThreaded("::mach_vm_read ( task = 0x%4.4x, addr = 0x%8.8llx, size = %llu, data => %8.8p, dataCnt => %i )", task, (uint64_t)curr_addr, (uint64_t)curr_size, vm_memory, curr_bytes_read); + + if (m_err.Success()) + { + if (curr_bytes_read != curr_size) + { + if (DNBLogCheckLogBit(LOG_MEMORY)) + m_err.LogThreaded("::mach_vm_read ( task = 0x%4.4x, addr = 0x%8.8llx, size = %llu, data => %8.8p, dataCnt=>%i ) only read %u of %llu bytes", task, (uint64_t)curr_addr, (uint64_t)curr_size, vm_memory, curr_bytes_read, curr_bytes_read, (uint64_t)curr_size); + } + ::memcpy (curr_data, (void *)vm_memory, curr_bytes_read); + ::vm_deallocate (mach_task_self (), vm_memory, curr_bytes_read); + total_bytes_read += curr_bytes_read; + curr_addr += curr_bytes_read; + curr_data += curr_bytes_read; + } + else + { + break; + } + } + return total_bytes_read; +} + + +nub_size_t +MachVMMemory::Write(task_t task, nub_addr_t address, const void *data, nub_size_t data_count) +{ + MachVMRegion vmRegion(task); + + nub_size_t total_bytes_written = 0; + nub_addr_t curr_addr = address; + const uint8_t *curr_data = (const uint8_t*)data; + + + while (total_bytes_written < data_count) + { + if (vmRegion.GetRegionForAddress(curr_addr)) + { + mach_vm_size_t curr_data_count = data_count - total_bytes_written; + mach_vm_size_t region_bytes_left = vmRegion.BytesRemaining(curr_addr); + if (region_bytes_left == 0) + { + break; + } + if (curr_data_count > region_bytes_left) + curr_data_count = region_bytes_left; + + if (vmRegion.SetProtections(curr_addr, curr_data_count, VM_PROT_READ | VM_PROT_WRITE)) + { + nub_size_t bytes_written = WriteRegion(task, curr_addr, curr_data, curr_data_count); + if (bytes_written <= 0) + { + // Error should have already be posted by WriteRegion... + break; + } + else + { + total_bytes_written += bytes_written; + curr_addr += bytes_written; + curr_data += bytes_written; + } + } + else + { + DNBLogThreadedIf(LOG_MEMORY_PROTECTIONS, "Failed to set read/write protections on region for address: [0x%8.8llx-0x%8.8llx)", (uint64_t)curr_addr, (uint64_t)(curr_addr + curr_data_count)); + break; + } + } + else + { + DNBLogThreadedIf(LOG_MEMORY_PROTECTIONS, "Failed to get region for address: 0x%8.8llx", (uint64_t)address); + break; + } + } + + return total_bytes_written; +} + + +nub_size_t +MachVMMemory::WriteRegion(task_t task, const nub_addr_t address, const void *data, const nub_size_t data_count) +{ + if (data == NULL || data_count == 0) + return 0; + + nub_size_t total_bytes_written = 0; + nub_addr_t curr_addr = address; + const uint8_t *curr_data = (const uint8_t*)data; + while (total_bytes_written < data_count) + { + mach_msg_type_number_t curr_data_count = MaxBytesLeftInPage(curr_addr, data_count - total_bytes_written); + m_err = ::mach_vm_write (task, curr_addr, (pointer_t) curr_data, curr_data_count); + if (DNBLogCheckLogBit(LOG_MEMORY) || m_err.Fail()) + m_err.LogThreaded("::mach_vm_write ( task = 0x%4.4x, addr = 0x%8.8llx, data = %8.8p, dataCnt = %u )", task, (uint64_t)curr_addr, curr_data, curr_data_count); + +#if !defined (__i386__) && !defined (__x86_64__) + vm_machine_attribute_val_t mattr_value = MATTR_VAL_CACHE_FLUSH; + + m_err = ::vm_machine_attribute (task, curr_addr, curr_data_count, MATTR_CACHE, &mattr_value); + if (DNBLogCheckLogBit(LOG_MEMORY) || m_err.Fail()) + m_err.LogThreaded("::vm_machine_attribute ( task = 0x%4.4x, addr = 0x%8.8llx, size = %u, attr = MATTR_CACHE, mattr_value => MATTR_VAL_CACHE_FLUSH )", task, (uint64_t)curr_addr, curr_data_count); +#endif + + if (m_err.Success()) + { + total_bytes_written += curr_data_count; + curr_addr += curr_data_count; + curr_data += curr_data_count; + } + else + { + break; + } + } + return total_bytes_written; +} diff --git a/lldb/tools/debugserver/source/MacOSX/MachVMMemory.h b/lldb/tools/debugserver/source/MacOSX/MachVMMemory.h new file mode 100644 index 00000000000..5635186854a --- /dev/null +++ b/lldb/tools/debugserver/source/MacOSX/MachVMMemory.h @@ -0,0 +1,40 @@ +//===-- MachVMMemory.h ------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/26/07. +// +//===----------------------------------------------------------------------===// + +#ifndef __MachVMMemory_h__ +#define __MachVMMemory_h__ + +#include "DNBDefs.h" +#include "DNBError.h" +#include <mach/mach.h> + +class MachVMMemory +{ +public: + enum { kInvalidPageSize = ~0 }; + MachVMMemory(); + ~MachVMMemory(); + nub_size_t Read(task_t task, nub_addr_t address, void *data, nub_size_t data_count); + nub_size_t Write(task_t task, nub_addr_t address, const void *data, nub_size_t data_count); + nub_size_t PageSize(); + +protected: + nub_size_t MaxBytesLeftInPage(nub_addr_t addr, nub_size_t count); + + nub_size_t WriteRegion(task_t task, const nub_addr_t address, const void *data, const nub_size_t data_count); + vm_size_t m_page_size; + DNBError m_err; +}; + + +#endif // #ifndef __MachVMMemory_h__ diff --git a/lldb/tools/debugserver/source/MacOSX/MachVMRegion.cpp b/lldb/tools/debugserver/source/MacOSX/MachVMRegion.cpp new file mode 100644 index 00000000000..6299cf4179f --- /dev/null +++ b/lldb/tools/debugserver/source/MacOSX/MachVMRegion.cpp @@ -0,0 +1,179 @@ +//===-- MachVMRegion.cpp ----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/26/07. +// +//===----------------------------------------------------------------------===// + +#include "MachVMRegion.h" +#include <mach/mach_vm.h> +#include "DNBLog.h" +#include <assert.h> + +MachVMRegion::MachVMRegion(task_t task) : + m_task(task), + m_addr(INVALID_NUB_ADDRESS), + m_err(), + m_start(INVALID_NUB_ADDRESS), + m_size(0), + m_depth(-1), + m_curr_protection(0), + m_protection_addr(INVALID_NUB_ADDRESS), + m_protection_size(0) +{ + memset(&m_data, 0, sizeof(m_data)); +} + +MachVMRegion::~MachVMRegion() +{ + // Restore any original protections and clear our vars + Clear(); +} + +void +MachVMRegion::Clear() +{ + RestoreProtections(); + m_addr = INVALID_NUB_ADDRESS; + m_err.Clear(); + m_start = INVALID_NUB_ADDRESS; + m_size = 0; + m_depth = -1; + memset(&m_data, 0, sizeof(m_data)); + m_curr_protection = 0; + m_protection_addr = INVALID_NUB_ADDRESS; + m_protection_size = 0; +} + +bool +MachVMRegion::SetProtections(mach_vm_address_t addr, mach_vm_size_t size, vm_prot_t prot) +{ + if (ContainsAddress(addr)) + { + mach_vm_size_t prot_size = size; + mach_vm_address_t end_addr = EndAddress(); + if (prot_size > (end_addr - addr)) + prot_size = end_addr - addr; + + if (prot_size > 0) + { + if (prot == (m_curr_protection & VM_PROT_ALL)) + { + DNBLogThreadedIf(LOG_MEMORY_PROTECTIONS | LOG_VERBOSE, "MachVMRegion::%s: protections (%u) already sufficient for task 0x%4.4x at address 0x%8.8llx) ", __FUNCTION__, prot, m_task, (uint64_t)addr); + // Protections are already set as requested... + return true; + } + else + { + m_err = ::mach_vm_protect (m_task, addr, prot_size, 0, prot); + if (DNBLogCheckLogBit(LOG_MEMORY_PROTECTIONS)) + m_err.LogThreaded("::mach_vm_protect ( task = 0x%4.4x, addr = 0x%8.8llx, size = %llu, set_max = %i, prot = %u )", m_task, (uint64_t)addr, (uint64_t)prot_size, 0, prot); + if (m_err.Fail()) + { + // Try again with the ability to create a copy on write region + m_err = ::mach_vm_protect (m_task, addr, prot_size, 0, prot | VM_PROT_COPY); + if (DNBLogCheckLogBit(LOG_MEMORY_PROTECTIONS) || m_err.Fail()) + m_err.LogThreaded("::mach_vm_protect ( task = 0x%4.4x, addr = 0x%8.8llx, size = %llu, set_max = %i, prot = %u )", m_task, (uint64_t)addr, (uint64_t)prot_size, 0, prot | VM_PROT_COPY); + } + if (m_err.Success()) + { + m_curr_protection = prot; + m_protection_addr = addr; + m_protection_size = prot_size; + return true; + } + } + } + else + { + DNBLogThreadedIf(LOG_MEMORY_PROTECTIONS | LOG_VERBOSE, "%s: Zero size for task 0x%4.4x at address 0x%8.8llx) ", __FUNCTION__, m_task, (uint64_t)addr); + } + } + return false; +} + +bool +MachVMRegion::RestoreProtections() +{ + if (m_curr_protection != m_data.protection && m_protection_size > 0) + { + m_err = ::mach_vm_protect (m_task, m_protection_addr, m_protection_size, 0, m_data.protection); + if (DNBLogCheckLogBit(LOG_MEMORY_PROTECTIONS) || m_err.Fail()) + m_err.LogThreaded("::mach_vm_protect ( task = 0x%4.4x, addr = 0x%8.8llx, size = %llu, set_max = %i, prot = %u )", m_task, (uint64_t)m_protection_addr, (uint64_t)m_protection_size, 0, m_data.protection); + if (m_err.Success()) + { + m_protection_size = 0; + m_protection_addr = INVALID_NUB_ADDRESS; + m_curr_protection = m_data.protection; + return true; + } + } + else + { + m_err.Clear(); + return true; + } + + return false; +} + +bool +MachVMRegion::GetRegionForAddress(nub_addr_t addr) +{ + // Restore any original protections and clear our vars + Clear(); + m_addr = addr; + m_start = addr; + m_depth = 1024; + mach_msg_type_number_t info_size = kRegionInfoSize; + assert(sizeof(info_size) == 4); + m_err = ::mach_vm_region_recurse (m_task, &m_start, &m_size, &m_depth, (vm_region_recurse_info_t)&m_data, &info_size); + if (DNBLogCheckLogBit(LOG_MEMORY_PROTECTIONS) || m_err.Fail()) + m_err.LogThreaded("::mach_vm_region_recurse ( task = 0x%4.4x, address => 0x%8.8llx, size => %llu, nesting_depth => %d, info => %p, infoCnt => %d) addr = 0x%8.8llx ", m_task, (uint64_t)m_start, (uint64_t)m_size, m_depth, &m_data, info_size, (uint64_t)addr); + if (m_err.Fail()) + { + return false; + } + else + { + if (DNBLogCheckLogBit(LOG_MEMORY_PROTECTIONS)) + { + DNBLogThreaded("info = { prot = %u, " + "max_prot = %u, " + "inheritance = 0x%8.8x, " + "offset = 0x%8.8llx, " + "user_tag = 0x%8.8x, " + "ref_count = %u, " + "shadow_depth = %u, " + "ext_pager = %u, " + "share_mode = %u, " + "is_submap = %d, " + "behavior = %d, " + "object_id = 0x%8.8x, " + "user_wired_count = 0x%4.4x }", + m_data.protection, + m_data.max_protection, + m_data.inheritance, + (uint64_t)m_data.offset, + m_data.user_tag, + m_data.ref_count, + m_data.shadow_depth, + m_data.external_pager, + m_data.share_mode, + m_data.is_submap, + m_data.behavior, + m_data.object_id, + m_data.user_wired_count); + } + } + + m_curr_protection = m_data.protection; + + return true; +} diff --git a/lldb/tools/debugserver/source/MacOSX/MachVMRegion.h b/lldb/tools/debugserver/source/MacOSX/MachVMRegion.h new file mode 100644 index 00000000000..617e221a57e --- /dev/null +++ b/lldb/tools/debugserver/source/MacOSX/MachVMRegion.h @@ -0,0 +1,67 @@ +//===-- MachVMRegion.h ------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/26/07. +// +//===----------------------------------------------------------------------===// + +#ifndef __MachVMRegion_h__ +#define __MachVMRegion_h__ + +#include "DNBDefs.h" +#include "DNBError.h" +#include <mach/mach.h> + +class MachVMRegion +{ +public: + MachVMRegion(task_t task); + ~MachVMRegion(); + + void Clear(); + mach_vm_address_t StartAddress() const { return m_start; } + mach_vm_address_t EndAddress() const { return m_start + m_size; } + mach_vm_address_t BytesRemaining(mach_vm_address_t addr) const + { + if (ContainsAddress(addr)) + return m_size - (addr - m_start); + else + return 0; + } + bool ContainsAddress(mach_vm_address_t addr) const + { + return addr >= StartAddress() && addr < EndAddress(); + } + + bool SetProtections(mach_vm_address_t addr, mach_vm_size_t size, vm_prot_t prot); + bool RestoreProtections(); + bool GetRegionForAddress(nub_addr_t addr); +protected: +#if defined (VM_REGION_SUBMAP_SHORT_INFO_COUNT_64) + typedef vm_region_submap_short_info_data_64_t RegionInfo; + enum { kRegionInfoSize = VM_REGION_SUBMAP_SHORT_INFO_COUNT_64 }; +#else + typedef vm_region_submap_info_data_64_t RegionInfo; + enum { kRegionInfoSize = VM_REGION_SUBMAP_INFO_COUNT_64 }; +#endif + + task_t m_task; + mach_vm_address_t m_addr; + DNBError m_err; + mach_vm_address_t m_start; + mach_vm_size_t m_size; + natural_t m_depth; + RegionInfo m_data; + vm_prot_t m_curr_protection; // The current, possibly modified protections. Original value is saved in m_data.protections. + mach_vm_address_t m_protection_addr; // The start address at which protections were changed + mach_vm_size_t m_protection_size; // The size of memory that had its protections changed + +}; + +#endif // #ifndef __MachVMRegion_h__ diff --git a/lldb/tools/debugserver/source/MacOSX/arm/DNBArchImpl.cpp b/lldb/tools/debugserver/source/MacOSX/arm/DNBArchImpl.cpp new file mode 100644 index 00000000000..6eec80cfd7a --- /dev/null +++ b/lldb/tools/debugserver/source/MacOSX/arm/DNBArchImpl.cpp @@ -0,0 +1,2610 @@ +//===-- DNBArchImpl.cpp -----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/25/07. +// +//===----------------------------------------------------------------------===// + +#if defined (__arm__) + +#include "MacOSX/arm/DNBArchImpl.h" +#include "MacOSX/MachProcess.h" +#include "MacOSX/MachThread.h" +#include "DNBBreakpoint.h" +#include "DNBLog.h" +#include "DNBRegisterInfo.h" +#include "DNB.h" + +#include <sys/sysctl.h> + +// BCR address match type +#define BCR_M_IMVA_MATCH ((uint32_t)(0u << 21)) +#define BCR_M_CONTEXT_ID_MATCH ((uint32_t)(1u << 21)) +#define BCR_M_IMVA_MISMATCH ((uint32_t)(2u << 21)) +#define BCR_M_RESERVED ((uint32_t)(3u << 21)) + +// Link a BVR/BCR or WVR/WCR pair to another +#define E_ENABLE_LINKING ((uint32_t)(1u << 20)) + +// Byte Address Select +#define BAS_IMVA_PLUS_0 ((uint32_t)(1u << 5)) +#define BAS_IMVA_PLUS_1 ((uint32_t)(1u << 6)) +#define BAS_IMVA_PLUS_2 ((uint32_t)(1u << 7)) +#define BAS_IMVA_PLUS_3 ((uint32_t)(1u << 8)) +#define BAS_IMVA_0_1 ((uint32_t)(3u << 5)) +#define BAS_IMVA_2_3 ((uint32_t)(3u << 7)) +#define BAS_IMVA_ALL ((uint32_t)(0xfu << 5)) + +// Break only in priveleged or user mode +#define S_RSVD ((uint32_t)(0u << 1)) +#define S_PRIV ((uint32_t)(1u << 1)) +#define S_USER ((uint32_t)(2u << 1)) +#define S_PRIV_USER ((S_PRIV) | (S_USER)) + +#define BCR_ENABLE ((uint32_t)(1u)) +#define WCR_ENABLE ((uint32_t)(1u)) + +// Watchpoint load/store +#define WCR_LOAD ((uint32_t)(1u << 3)) +#define WCR_STORE ((uint32_t)(1u << 4)) + +//#define DNB_ARCH_MACH_ARM_DEBUG_SW_STEP 1 + +static const uint8_t g_arm_breakpoint_opcode[] = { 0xFE, 0xDE, 0xFF, 0xE7 }; +static const uint8_t g_thumb_breakpooint_opcode[] = { 0xFE, 0xDE }; + +// ARM constants used during decoding +#define REG_RD 0 +#define LDM_REGLIST 1 +#define PC_REG 15 +#define PC_REGLIST_BIT 0x8000 + +// ARM conditions +#define COND_EQ 0x0 +#define COND_NE 0x1 +#define COND_CS 0x2 +#define COND_HS 0x2 +#define COND_CC 0x3 +#define COND_LO 0x3 +#define COND_MI 0x4 +#define COND_PL 0x5 +#define COND_VS 0x6 +#define COND_VC 0x7 +#define COND_HI 0x8 +#define COND_LS 0x9 +#define COND_GE 0xA +#define COND_LT 0xB +#define COND_GT 0xC +#define COND_LE 0xD +#define COND_AL 0xE +#define COND_UNCOND 0xF + +#define MASK_CPSR_T (1u << 5) +#define MASK_CPSR_J (1u << 24) + +#define MNEMONIC_STRING_SIZE 32 +#define OPERAND_STRING_SIZE 128 + +const uint8_t * const +DNBArchMachARM::SoftwareBreakpointOpcode (nub_size_t byte_size) +{ + switch (byte_size) + { + case 2: return g_thumb_breakpooint_opcode; + case 4: return g_arm_breakpoint_opcode; + } + return NULL; +} + +uint32_t +DNBArchMachARM::GetCPUType() +{ + return CPU_TYPE_ARM; +} + +uint64_t +DNBArchMachARM::GetPC(uint64_t failValue) +{ + // Get program counter + if (GetGPRState(false) == KERN_SUCCESS) + return m_state.gpr.__pc; + return failValue; +} + +kern_return_t +DNBArchMachARM::SetPC(uint64_t value) +{ + // Get program counter + kern_return_t err = GetGPRState(false); + if (err == KERN_SUCCESS) + { + m_state.gpr.__pc = value; + err = SetGPRState(); + } + return err == KERN_SUCCESS; +} + +uint64_t +DNBArchMachARM::GetSP(uint64_t failValue) +{ + // Get stack pointer + if (GetGPRState(false) == KERN_SUCCESS) + return m_state.gpr.__sp; + return failValue; +} + +kern_return_t +DNBArchMachARM::GetGPRState(bool force) +{ + int set = e_regSetGPR; + // Check if we have valid cached registers + if (!force && m_state.GetError(set, Read) == KERN_SUCCESS) + return KERN_SUCCESS; + + // Read the registers from our thread + mach_msg_type_number_t count = ARM_THREAD_STATE_COUNT; + kern_return_t kret = ::thread_get_state(m_thread->ThreadID(), ARM_THREAD_STATE, (thread_state_t)&m_state.gpr, &count); + uint32_t *r = &m_state.gpr.__r[0]; + DNBLogThreadedIf(LOG_THREAD, "thread_get_state(0x%4.4x, %u, &gpr, %u) => 0x%8.8x regs r0=%8.8x r1=%8.8x r2=%8.8x r3=%8.8x r4=%8.8x r5=%8.8x r6=%8.8x r7=%8.8x r8=%8.8x r9=%8.8x r10=%8.8x r11=%8.8x s12=%8.8x sp=%8.8x lr=%8.8x pc=%8.8x cpsr=%8.8x", m_thread->ThreadID(), ARM_THREAD_STATE, ARM_THREAD_STATE_COUNT, kret, + r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10], r[11], r[12], r[13], r[14], r[15], r[16]); + m_state.SetError(set, Read, kret); + return kret; +} + +kern_return_t +DNBArchMachARM::GetVFPState(bool force) +{ + int set = e_regSetVFP; + // Check if we have valid cached registers + if (!force && m_state.GetError(set, Read) == KERN_SUCCESS) + return KERN_SUCCESS; + + // Read the registers from our thread + mach_msg_type_number_t count = ARM_VFP_STATE_COUNT; + kern_return_t kret = ::thread_get_state(m_thread->ThreadID(), ARM_VFP_STATE, (thread_state_t)&m_state.vfp, &count); + m_state.SetError(set, Read, kret); + return kret; +} + +kern_return_t +DNBArchMachARM::GetEXCState(bool force) +{ + int set = e_regSetEXC; + // Check if we have valid cached registers + if (!force && m_state.GetError(set, Read) == KERN_SUCCESS) + return KERN_SUCCESS; + + // Read the registers from our thread + mach_msg_type_number_t count = ARM_EXCEPTION_STATE_COUNT; + kern_return_t kret = ::thread_get_state(m_thread->ThreadID(), ARM_EXCEPTION_STATE, (thread_state_t)&m_state.exc, &count); + m_state.SetError(set, Read, kret); + return kret; +} + +static void +DumpDBGState(const arm_debug_state_t& dbg) +{ + uint32_t i = 0; + for (i=0; i<16; i++) + DNBLogThreadedIf(LOG_STEP, "BVR%-2u/BCR%-2u = { 0x%8.8x, 0x%8.8x } WVR%-2u/WCR%-2u = { 0x%8.8x, 0x%8.8x }", + i, i, dbg.__bvr[i], dbg.__bcr[i], + i, i, dbg.__wvr[i], dbg.__wcr[i]); +} + +kern_return_t +DNBArchMachARM::GetDBGState(bool force) +{ + int set = e_regSetDBG; + + // Check if we have valid cached registers + if (!force && m_state.GetError(set, Read) == KERN_SUCCESS) + return KERN_SUCCESS; + + // Read the registers from our thread + mach_msg_type_number_t count = ARM_DEBUG_STATE_COUNT; + kern_return_t kret = ::thread_get_state(m_thread->ThreadID(), ARM_DEBUG_STATE, (thread_state_t)&m_state.dbg, &count); + m_state.SetError(set, Read, kret); + return kret; +} + +kern_return_t +DNBArchMachARM::SetGPRState() +{ + int set = e_regSetGPR; + kern_return_t kret = ::thread_set_state(m_thread->ThreadID(), ARM_THREAD_STATE, (thread_state_t)&m_state.gpr, ARM_THREAD_STATE_COUNT); + m_state.SetError(set, Write, kret); // Set the current write error for this register set + m_state.InvalidateRegisterSetState(set); // Invalidate the current register state in case registers are read back differently + return kret; // Return the error code +} + +kern_return_t +DNBArchMachARM::SetVFPState() +{ + int set = e_regSetVFP; + kern_return_t kret = ::thread_set_state (m_thread->ThreadID(), ARM_VFP_STATE, (thread_state_t)&m_state.vfp, ARM_VFP_STATE_COUNT); + m_state.SetError(set, Write, kret); // Set the current write error for this register set + m_state.InvalidateRegisterSetState(set); // Invalidate the current register state in case registers are read back differently + return kret; // Return the error code +} + +kern_return_t +DNBArchMachARM::SetEXCState() +{ + int set = e_regSetEXC; + kern_return_t kret = ::thread_set_state (m_thread->ThreadID(), ARM_EXCEPTION_STATE, (thread_state_t)&m_state.exc, ARM_EXCEPTION_STATE_COUNT); + m_state.SetError(set, Write, kret); // Set the current write error for this register set + m_state.InvalidateRegisterSetState(set); // Invalidate the current register state in case registers are read back differently + return kret; // Return the error code +} + +kern_return_t +DNBArchMachARM::SetDBGState() +{ + int set = e_regSetDBG; + kern_return_t kret = ::thread_set_state (m_thread->ThreadID(), ARM_DEBUG_STATE, (thread_state_t)&m_state.dbg, ARM_DEBUG_STATE_COUNT); + m_state.SetError(set, Write, kret); // Set the current write error for this register set + m_state.InvalidateRegisterSetState(set); // Invalidate the current register state in case registers are read back differently + return kret; // Return the error code +} + +void +DNBArchMachARM::ThreadWillResume() +{ + // Do we need to step this thread? If so, let the mach thread tell us so. + if (m_thread->IsStepping()) + { + bool step_handled = false; + // This is the primary thread, let the arch do anything it needs + if (NumSupportedHardwareBreakpoints() > 0) + { +#if defined (DNB_ARCH_MACH_ARM_DEBUG_SW_STEP) + bool half_step = m_hw_single_chained_step_addr != INVALID_NUB_ADDRESS; +#endif + step_handled = EnableHardwareSingleStep(true) == KERN_SUCCESS; +#if defined (DNB_ARCH_MACH_ARM_DEBUG_SW_STEP) + if (!half_step) + step_handled = false; +#endif + } + + if (!step_handled) + { + SetSingleStepSoftwareBreakpoints(); + } + } +} + +bool +DNBArchMachARM::ThreadDidStop() +{ + bool success = true; + + m_state.InvalidateRegisterSetState (e_regSetALL); + + // Are we stepping a single instruction? + if (GetGPRState(true) == KERN_SUCCESS) + { + // We are single stepping, was this the primary thread? + if (m_thread->IsStepping()) + { +#if defined (DNB_ARCH_MACH_ARM_DEBUG_SW_STEP) + success = EnableHardwareSingleStep(false) == KERN_SUCCESS; + // Hardware single step must work if we are going to test software + // single step functionality + assert(success); + if (m_hw_single_chained_step_addr == INVALID_NUB_ADDRESS && m_sw_single_step_next_pc != INVALID_NUB_ADDRESS) + { + uint32_t sw_step_next_pc = m_sw_single_step_next_pc & 0xFFFFFFFEu; + bool sw_step_next_pc_is_thumb = (m_sw_single_step_next_pc & 1) != 0; + bool actual_next_pc_is_thumb = (m_state.gpr.__cpsr & 0x20) != 0; + if (m_state.gpr.__pc != sw_step_next_pc) + { + DNBLogError("curr pc = 0x%8.8x - calculated single step target PC was incorrect: 0x%8.8x != 0x%8.8x", m_state.gpr.__pc, sw_step_next_pc, m_state.gpr.__pc); + exit(1); + } + if (actual_next_pc_is_thumb != sw_step_next_pc_is_thumb) + { + DNBLogError("curr pc = 0x%8.8x - calculated single step calculated mode mismatch: sw single mode = %s != %s", + m_state.gpr.__pc, + actual_next_pc_is_thumb ? "Thumb" : "ARM", + sw_step_next_pc_is_thumb ? "Thumb" : "ARM"); + exit(1); + } + m_sw_single_step_next_pc = INVALID_NUB_ADDRESS; + } +#else + // Are we software single stepping? + if (NUB_BREAK_ID_IS_VALID(m_sw_single_step_break_id) || m_sw_single_step_itblock_break_count) + { + // Remove any software single stepping breakpoints that we have set + + // Do we have a normal software single step breakpoint? + if (NUB_BREAK_ID_IS_VALID(m_sw_single_step_break_id)) + { + DNBLogThreadedIf(LOG_STEP, "%s: removing software single step breakpoint (breakID=%d)", __FUNCTION__, m_sw_single_step_break_id); + success = m_thread->Process()->DisableBreakpoint(m_sw_single_step_break_id, true); + m_sw_single_step_break_id = INVALID_NUB_BREAK_ID; + } + + // Do we have any Thumb IT breakpoints? + if (m_sw_single_step_itblock_break_count > 0) + { + // See if we hit one of our Thumb IT breakpoints? + DNBBreakpoint *step_bp = m_thread->Process()->Breakpoints().FindByAddress(m_state.gpr.__pc); + + if (step_bp) + { + // We did hit our breakpoint, tell the breakpoint it was + // hit so that it can run its callback routine and fixup + // the PC. + DNBLogThreadedIf(LOG_STEP, "%s: IT software single step breakpoint hit (breakID=%u)", __FUNCTION__, step_bp->GetID()); + step_bp->BreakpointHit(m_thread->Process()->ProcessID(), m_thread->ThreadID()); + } + + // Remove all Thumb IT breakpoints + for (int i = 0; i < m_sw_single_step_itblock_break_count; i++) + { + if (NUB_BREAK_ID_IS_VALID(m_sw_single_step_itblock_break_id[i])) + { + DNBLogThreadedIf(LOG_STEP, "%s: removing IT software single step breakpoint (breakID=%d)", __FUNCTION__, m_sw_single_step_itblock_break_id[i]); + success = m_thread->Process()->DisableBreakpoint(m_sw_single_step_itblock_break_id[i], true); + m_sw_single_step_itblock_break_id[i] = INVALID_NUB_BREAK_ID; + } + } + m_sw_single_step_itblock_break_count = 0; + + // Decode instructions up to the current PC to ensure the internal decoder state is valid for the IT block + // The decoder has to decode each instruction in the IT block even if it is not executed so that + // the fields are correctly updated + DecodeITBlockInstructions(m_state.gpr.__pc); + } + + } + else + success = EnableHardwareSingleStep(false) == KERN_SUCCESS; +#endif + } + else + { + // The MachThread will automatically restore the suspend count + // in ThreadDidStop(), so we don't need to do anything here if + // we weren't the primary thread the last time + } + } + return success; +} + +bool +DNBArchMachARM::StepNotComplete () +{ + if (m_hw_single_chained_step_addr != INVALID_NUB_ADDRESS) + { + kern_return_t kret = KERN_INVALID_ARGUMENT; + kret = GetGPRState(false); + if (kret == KERN_SUCCESS) + { + if (m_state.gpr.__pc == m_hw_single_chained_step_addr) + { + DNBLogThreadedIf(LOG_STEP, "Need to step some more at 0x%8.8x", m_hw_single_chained_step_addr); + return true; + } + } + } + + m_hw_single_chained_step_addr = INVALID_NUB_ADDRESS; + return false; +} + + +void +DNBArchMachARM::DecodeITBlockInstructions(nub_addr_t curr_pc) + +{ + uint16_t opcode16; + uint32_t opcode32; + nub_addr_t next_pc_in_itblock; + nub_addr_t pc_in_itblock = m_last_decode_pc; + + DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: last_decode_pc=0x%8.8x", __FUNCTION__, m_last_decode_pc); + + // Decode IT block instruction from the instruction following the m_last_decoded_instruction at + // PC m_last_decode_pc upto and including the instruction at curr_pc + if (m_thread->Process()->Task().ReadMemory(pc_in_itblock, 2, &opcode16) == 2) + { + opcode32 = opcode16; + pc_in_itblock += 2; + // Check for 32 bit thumb opcode and read the upper 16 bits if needed + if (((opcode32 & 0xE000) == 0xE000) && opcode32 & 0x1800) + { + // Adjust 'next_pc_in_itblock' to point to the default next Thumb instruction for + // a 32 bit Thumb opcode + // Read bits 31:16 of a 32 bit Thumb opcode + if (m_thread->Process()->Task().ReadMemory(pc_in_itblock, 2, &opcode16) == 2) + { + pc_in_itblock += 2; + // 32 bit thumb opcode + opcode32 = (opcode32 << 16) | opcode16; + } + else + { + DNBLogError("%s: Unable to read opcode bits 31:16 for a 32 bit thumb opcode at pc=0x%8.8lx", __FUNCTION__, pc_in_itblock); + } + } + } + else + { + DNBLogError("%s: Error reading 16-bit Thumb instruction at pc=0x%8.8x", __FUNCTION__, pc_in_itblock); + } + + DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: pc_in_itblock=0x%8.8x, curr_pc=0x%8.8x", __FUNCTION__, pc_in_itblock, curr_pc); + + next_pc_in_itblock = pc_in_itblock; + while (next_pc_in_itblock <= curr_pc) + { + arm_error_t decodeError; + + m_last_decode_pc = pc_in_itblock; + decodeError = DecodeInstructionUsingDisassembler(pc_in_itblock, m_state.gpr.__cpsr, &m_last_decode_arm, &m_last_decode_thumb, &next_pc_in_itblock); + + pc_in_itblock = next_pc_in_itblock; + DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: next_pc_in_itblock=0x%8.8x", __FUNCTION__, next_pc_in_itblock); + } +} + + +// Set the single step bit in the processor status register. +kern_return_t +DNBArchMachARM::EnableHardwareSingleStep (bool enable) +{ + DNBError err; + DNBLogThreadedIf(LOG_STEP, "%s( enable = %d )", __FUNCTION__, enable); + + err = GetGPRState(false); + + if (err.Fail()) + { + err.LogThreaded("%s: failed to read the GPR registers", __FUNCTION__); + return err.Error(); + } + + err = GetDBGState(false); + + if (err.Fail()) + { + err.LogThreaded("%s: failed to read the DBG registers", __FUNCTION__); + return err.Error(); + } + + const uint32_t i = 0; + if (enable) + { + m_hw_single_chained_step_addr = INVALID_NUB_ADDRESS; + + // Save our previous state + m_dbg_save = m_state.dbg; + // Set a breakpoint that will stop when the PC doesn't match the current one! + m_state.dbg.__bvr[i] = m_state.gpr.__pc & 0xFFFFFFFCu; // Set the current PC as the breakpoint address + m_state.dbg.__bcr[i] = BCR_M_IMVA_MISMATCH | // Stop on address mismatch + S_USER | // Stop only in user mode + BCR_ENABLE; // Enable this breakpoint + if (m_state.gpr.__cpsr & 0x20) + { + // Thumb breakpoint + if (m_state.gpr.__pc & 2) + m_state.dbg.__bcr[i] |= BAS_IMVA_2_3; + else + m_state.dbg.__bcr[i] |= BAS_IMVA_0_1; + + uint16_t opcode; + if (sizeof(opcode) == m_thread->Process()->Task().ReadMemory(m_state.gpr.__pc, sizeof(opcode), &opcode)) + { + if (((opcode & 0xE000) == 0xE000) && opcode & 0x1800) + { + // 32 bit thumb opcode... + if (m_state.gpr.__pc & 2) + { + // We can't take care of a 32 bit thumb instruction single step + // with just IVA mismatching. We will need to chain an extra + // hardware single step in order to complete this single step... + m_hw_single_chained_step_addr = m_state.gpr.__pc + 2; + } + else + { + // Extend the number of bits to ignore for the mismatch + m_state.dbg.__bcr[i] |= BAS_IMVA_ALL; + } + } + } + } + else + { + // ARM breakpoint + m_state.dbg.__bcr[i] |= BAS_IMVA_ALL; // Stop when any address bits change + } + + DNBLogThreadedIf(LOG_STEP, "%s: BVR%u=0x%8.8x BCR%u=0x%8.8x", __FUNCTION__, i, m_state.dbg.__bvr[i], i, m_state.dbg.__bcr[i]); + + for (uint32_t j=i+1; j<16; ++j) + { + // Disable all others + m_state.dbg.__bvr[j] = 0; + m_state.dbg.__bcr[j] = 0; + } + } + else + { + // Just restore the state we had before we did single stepping + m_state.dbg = m_dbg_save; + } + + return SetDBGState(); +} + +// return 1 if bit "BIT" is set in "value" +static inline uint32_t bit(uint32_t value, uint32_t bit) +{ + return (value >> bit) & 1u; +} + +// return the bitfield "value[msbit:lsbit]". +static inline uint32_t bits(uint32_t value, uint32_t msbit, uint32_t lsbit) +{ + assert(msbit >= lsbit); + uint32_t shift_left = sizeof(value) * 8 - 1 - msbit; + value <<= shift_left; // shift anything above the msbit off of the unsigned edge + value >>= shift_left + lsbit; // shift it back again down to the lsbit (including undoing any shift from above) + return value; // return our result +} + +bool +DNBArchMachARM::ConditionPassed(uint8_t condition, uint32_t cpsr) +{ + uint32_t cpsr_n = bit(cpsr, 31); // Negative condition code flag + uint32_t cpsr_z = bit(cpsr, 30); // Zero condition code flag + uint32_t cpsr_c = bit(cpsr, 29); // Carry condition code flag + uint32_t cpsr_v = bit(cpsr, 28); // Overflow condition code flag + + switch (condition) { + case COND_EQ: // (0x0) + if (cpsr_z == 1) return true; + break; + case COND_NE: // (0x1) + if (cpsr_z == 0) return true; + break; + case COND_CS: // (0x2) + if (cpsr_c == 1) return true; + break; + case COND_CC: // (0x3) + if (cpsr_c == 0) return true; + break; + case COND_MI: // (0x4) + if (cpsr_n == 1) return true; + break; + case COND_PL: // (0x5) + if (cpsr_n == 0) return true; + break; + case COND_VS: // (0x6) + if (cpsr_v == 1) return true; + break; + case COND_VC: // (0x7) + if (cpsr_v == 0) return true; + break; + case COND_HI: // (0x8) + if ((cpsr_c == 1) && (cpsr_z == 0)) return true; + break; + case COND_LS: // (0x9) + if ((cpsr_c == 0) || (cpsr_z == 1)) return true; + break; + case COND_GE: // (0xA) + if (cpsr_n == cpsr_v) return true; + break; + case COND_LT: // (0xB) + if (cpsr_n != cpsr_v) return true; + break; + case COND_GT: // (0xC) + if ((cpsr_z == 0) && (cpsr_n == cpsr_v)) return true; + break; + case COND_LE: // (0xD) + if ((cpsr_z == 1) || (cpsr_n != cpsr_v)) return true; + break; + default: + return true; + break; + } + + return false; +} + +bool +DNBArchMachARM::ComputeNextPC(nub_addr_t currentPC, arm_decoded_instruction_t decodedInstruction, bool currentPCIsThumb, nub_addr_t *targetPC) +{ + nub_addr_t myTargetPC, addressWherePCLives; + pid_t mypid; + + uint32_t cpsr_c = bit(m_state.gpr.__cpsr, 29); // Carry condition code flag + + uint32_t firstOperand=0, secondOperand=0, shiftAmount=0, secondOperandAfterShift=0, immediateValue=0; + uint32_t halfwords=0, baseAddress=0, immediateOffset=0, addressOffsetFromRegister=0, addressOffsetFromRegisterAfterShift; + uint32_t baseAddressIndex=INVALID_NUB_HW_INDEX; + uint32_t firstOperandIndex=INVALID_NUB_HW_INDEX; + uint32_t secondOperandIndex=INVALID_NUB_HW_INDEX; + uint32_t addressOffsetFromRegisterIndex=INVALID_NUB_HW_INDEX; + uint32_t shiftRegisterIndex=INVALID_NUB_HW_INDEX; + uint16_t registerList16, registerList16NoPC; + uint8_t registerList8; + uint32_t numRegistersToLoad=0; + + DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: instruction->code=%d", __FUNCTION__, decodedInstruction.instruction->code); + + // Get the following in this switch statement: + // - firstOperand, secondOperand, immediateValue, shiftAmount: For arithmetic, logical and move instructions + // - baseAddress, immediateOffset, shiftAmount: For LDR + // - numRegistersToLoad: For LDM and POP instructions + switch (decodedInstruction.instruction->code) + { + // Arithmetic operations that can change the PC + case ARM_INST_ADC: + case ARM_INST_ADCS: + case ARM_INST_ADD: + case ARM_INST_ADDS: + case ARM_INST_AND: + case ARM_INST_ANDS: + case ARM_INST_ASR: + case ARM_INST_ASRS: + case ARM_INST_BIC: + case ARM_INST_BICS: + case ARM_INST_EOR: + case ARM_INST_EORS: + case ARM_INST_ORR: + case ARM_INST_ORRS: + case ARM_INST_RSB: + case ARM_INST_RSBS: + case ARM_INST_RSC: + case ARM_INST_RSCS: + case ARM_INST_SBC: + case ARM_INST_SBCS: + case ARM_INST_SUB: + case ARM_INST_SUBS: + switch (decodedInstruction.addressMode) + { + case ARM_ADDR_DATA_IMM: + if (decodedInstruction.numOperands != 3) + { + DNBLogError("Expected 3 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); + return false; + } + + if (decodedInstruction.op[0].value != PC_REG) + { + DNBLogError("Destination register is not a PC! %s routine should be called on on instructions that modify the PC. Destination register is R%d!", __FUNCTION__, decodedInstruction.op[0].value); + return false; + } + + // Get firstOperand register value (at index=1) + firstOperandIndex = decodedInstruction.op[1].value; // first operand register index + firstOperand = m_state.gpr.__r[firstOperandIndex]; + + // Get immediateValue (at index=2) + immediateValue = decodedInstruction.op[2].value; + + break; + + case ARM_ADDR_DATA_REG: + if (decodedInstruction.numOperands != 3) + { + DNBLogError("Expected 3 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); + return false; + } + + if (decodedInstruction.op[0].value != PC_REG) + { + DNBLogError("Destination register is not a PC! %s routine should be called on on instructions that modify the PC. Destination register is R%d!", __FUNCTION__, decodedInstruction.op[0].value); + return false; + } + + // Get firstOperand register value (at index=1) + firstOperandIndex = decodedInstruction.op[1].value; // first operand register index + firstOperand = m_state.gpr.__r[firstOperandIndex]; + + // Get secondOperand register value (at index=2) + secondOperandIndex = decodedInstruction.op[2].value; // second operand register index + secondOperand = m_state.gpr.__r[secondOperandIndex]; + + break; + + case ARM_ADDR_DATA_SCALED_IMM: + if (decodedInstruction.numOperands != 4) + { + DNBLogError("Expected 4 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); + return false; + } + + if (decodedInstruction.op[0].value != PC_REG) + { + DNBLogError("Destination register is not a PC! %s routine should be called on on instructions that modify the PC. Destination register is R%d!", __FUNCTION__, decodedInstruction.op[0].value); + return false; + } + + // Get firstOperand register value (at index=1) + firstOperandIndex = decodedInstruction.op[1].value; // first operand register index + firstOperand = m_state.gpr.__r[firstOperandIndex]; + + // Get secondOperand register value (at index=2) + secondOperandIndex = decodedInstruction.op[2].value; // second operand register index + secondOperand = m_state.gpr.__r[secondOperandIndex]; + + // Get shiftAmount as immediate value (at index=3) + shiftAmount = decodedInstruction.op[3].value; + + break; + + + case ARM_ADDR_DATA_SCALED_REG: + if (decodedInstruction.numOperands != 4) + { + DNBLogError("Expected 4 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); + return false; + } + + if (decodedInstruction.op[0].value != PC_REG) + { + DNBLogError("Destination register is not a PC! %s routine should be called on on instructions that modify the PC. Destination register is R%d!", __FUNCTION__, decodedInstruction.op[0].value); + return false; + } + + // Get firstOperand register value (at index=1) + firstOperandIndex = decodedInstruction.op[1].value; // first operand register index + firstOperand = m_state.gpr.__r[firstOperandIndex]; + + // Get secondOperand register value (at index=2) + secondOperandIndex = decodedInstruction.op[2].value; // second operand register index + secondOperand = m_state.gpr.__r[secondOperandIndex]; + + // Get shiftAmount from register (at index=3) + shiftRegisterIndex = decodedInstruction.op[3].value; // second operand register index + shiftAmount = m_state.gpr.__r[shiftRegisterIndex]; + + break; + + case THUMB_ADDR_HR_HR: + if (decodedInstruction.numOperands != 2) + { + DNBLogError("Expected 2 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); + return false; + } + + if (decodedInstruction.op[0].value != PC_REG) + { + DNBLogError("Destination register is not a PC! %s routine should be called on on instructions that modify the PC. Destination register is R%d!", __FUNCTION__, decodedInstruction.op[0].value); + return false; + } + + // Get firstOperand register value (at index=0) + firstOperandIndex = decodedInstruction.op[0].value; // first operand register index + firstOperand = m_state.gpr.__r[firstOperandIndex]; + + // Get secondOperand register value (at index=1) + secondOperandIndex = decodedInstruction.op[1].value; // second operand register index + secondOperand = m_state.gpr.__r[secondOperandIndex]; + + break; + + default: + break; + } + break; + + // Logical shifts and move operations that can change the PC + case ARM_INST_LSL: + case ARM_INST_LSLS: + case ARM_INST_LSR: + case ARM_INST_LSRS: + case ARM_INST_MOV: + case ARM_INST_MOVS: + case ARM_INST_MVN: + case ARM_INST_MVNS: + case ARM_INST_ROR: + case ARM_INST_RORS: + case ARM_INST_RRX: + case ARM_INST_RRXS: + // In these cases, the firstOperand is always 0, as if it does not exist + switch (decodedInstruction.addressMode) + { + case ARM_ADDR_DATA_IMM: + if (decodedInstruction.numOperands != 2) + { + DNBLogError("Expected 2 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); + return false; + } + + if (decodedInstruction.op[0].value != PC_REG) + { + DNBLogError("Destination register is not a PC! %s routine should be called on on instructions that modify the PC. Destination register is R%d!", __FUNCTION__, decodedInstruction.op[0].value); + return false; + } + + // Get immediateValue (at index=1) + immediateValue = decodedInstruction.op[1].value; + + break; + + case ARM_ADDR_DATA_REG: + if (decodedInstruction.numOperands != 2) + { + DNBLogError("Expected 2 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); + return false; + } + + if (decodedInstruction.op[0].value != PC_REG) + { + DNBLogError("Destination register is not a PC! %s routine should be called on on instructions that modify the PC. Destination register is R%d!", __FUNCTION__, decodedInstruction.op[0].value); + return false; + } + + // Get secondOperand register value (at index=1) + secondOperandIndex = decodedInstruction.op[1].value; // second operand register index + secondOperand = m_state.gpr.__r[secondOperandIndex]; + + break; + + case ARM_ADDR_DATA_SCALED_IMM: + if (decodedInstruction.numOperands != 3) + { + DNBLogError("Expected 4 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); + return false; + } + + if (decodedInstruction.op[0].value != PC_REG) + { + DNBLogError("Destination register is not a PC! %s routine should be called on on instructions that modify the PC. Destination register is R%d!", __FUNCTION__, decodedInstruction.op[0].value); + return false; + } + + // Get secondOperand register value (at index=1) + secondOperandIndex = decodedInstruction.op[2].value; // second operand register index + secondOperand = m_state.gpr.__r[secondOperandIndex]; + + // Get shiftAmount as immediate value (at index=2) + shiftAmount = decodedInstruction.op[2].value; + + break; + + + case ARM_ADDR_DATA_SCALED_REG: + if (decodedInstruction.numOperands != 3) + { + DNBLogError("Expected 3 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); + return false; + } + + if (decodedInstruction.op[0].value != PC_REG) + { + DNBLogError("Destination register is not a PC! %s routine should be called on on instructions that modify the PC. Destination register is R%d!", __FUNCTION__, decodedInstruction.op[0].value); + return false; + } + + // Get secondOperand register value (at index=1) + secondOperandIndex = decodedInstruction.op[1].value; // second operand register index + secondOperand = m_state.gpr.__r[secondOperandIndex]; + + // Get shiftAmount from register (at index=2) + shiftRegisterIndex = decodedInstruction.op[2].value; // second operand register index + shiftAmount = m_state.gpr.__r[shiftRegisterIndex]; + + break; + + case THUMB_ADDR_HR_HR: + if (decodedInstruction.numOperands != 2) + { + DNBLogError("Expected 2 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); + return false; + } + + if (decodedInstruction.op[0].value != PC_REG) + { + DNBLogError("Destination register is not a PC! %s routine should be called on on instructions that modify the PC. Destination register is R%d!", __FUNCTION__, decodedInstruction.op[0].value); + return false; + } + + // Get secondOperand register value (at index=1) + secondOperandIndex = decodedInstruction.op[1].value; // second operand register index + secondOperand = m_state.gpr.__r[secondOperandIndex]; + + break; + + default: + break; + } + + break; + + // Simple branches, used to hop around within a routine + case ARM_INST_B: + *targetPC = decodedInstruction.targetPC; // Known targetPC + return true; + break; + + // Branch-and-link, used to call ARM subroutines + case ARM_INST_BL: + *targetPC = decodedInstruction.targetPC; // Known targetPC + return true; + break; + + // Branch-and-link with exchange, used to call opposite-mode subroutines + case ARM_INST_BLX: + if ((decodedInstruction.addressMode == ARM_ADDR_BRANCH_IMM) || + (decodedInstruction.addressMode == THUMB_ADDR_UNCOND)) + { + *targetPC = decodedInstruction.targetPC; // Known targetPC + return true; + } + else // addressMode == ARM_ADDR_BRANCH_REG + { + // Unknown target unless we're branching to the PC itself, + // although this may not work properly with BLX + if (decodedInstruction.op[REG_RD].value == PC_REG) + { + // this should (almost) never happen + *targetPC = decodedInstruction.targetPC; // Known targetPC + return true; + } + + // Get the branch address and return + if (decodedInstruction.numOperands != 1) + { + DNBLogError("Expected 1 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); + return false; + } + + // Get branch address in register (at index=0) + *targetPC = m_state.gpr.__r[decodedInstruction.op[0].value]; + return true; + } + break; + + // Branch with exchange, used to hop to opposite-mode code + // Branch to Jazelle code, used to execute Java; included here since it + // acts just like BX unless the Jazelle unit is active and JPC is + // already loaded into it. + case ARM_INST_BX: + case ARM_INST_BXJ: + // Unknown target unless we're branching to the PC itself, + // although this can never switch to Thumb mode and is + // therefore pretty much useless + if (decodedInstruction.op[REG_RD].value == PC_REG) + { + // this should (almost) never happen + *targetPC = decodedInstruction.targetPC; // Known targetPC + return true; + } + + // Get the branch address and return + if (decodedInstruction.numOperands != 1) + { + DNBLogError("Expected 1 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); + return false; + } + + // Get branch address in register (at index=0) + *targetPC = m_state.gpr.__r[decodedInstruction.op[0].value]; + return true; + break; + + // Compare and branch on zero/non-zero (Thumb-16 only) + // Unusual condition check built into the instruction + case ARM_INST_CBZ: + case ARM_INST_CBNZ: + // Branch address is known at compile time + // Get the branch address and return + if (decodedInstruction.numOperands != 2) + { + DNBLogError("Expected 2 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); + return false; + } + + // Get branch address as an immediate value (at index=1) + *targetPC = decodedInstruction.op[1].value; + return true; + break; + + // Load register can be used to load PC, usually with a function pointer + case ARM_INST_LDR: + if (decodedInstruction.op[REG_RD].value != PC_REG) + { + DNBLogError("Destination register is not a PC! %s routine should be called on on instructions that modify the PC. Destination register is R%d!", __FUNCTION__, decodedInstruction.op[0].value); + return false; + } + switch (decodedInstruction.addressMode) + { + case ARM_ADDR_LSWUB_IMM: + case ARM_ADDR_LSWUB_IMM_PRE: + case ARM_ADDR_LSWUB_IMM_POST: + if (decodedInstruction.numOperands != 3) + { + DNBLogError("Expected 3 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); + return false; + } + + // Get baseAddress from register (at index=1) + baseAddressIndex = decodedInstruction.op[1].value; + baseAddress = m_state.gpr.__r[baseAddressIndex]; + + // Get immediateOffset (at index=2) + immediateOffset = decodedInstruction.op[2].value; + break; + + case ARM_ADDR_LSWUB_REG: + case ARM_ADDR_LSWUB_REG_PRE: + case ARM_ADDR_LSWUB_REG_POST: + if (decodedInstruction.numOperands != 3) + { + DNBLogError("Expected 3 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); + return false; + } + + // Get baseAddress from register (at index=1) + baseAddressIndex = decodedInstruction.op[1].value; + baseAddress = m_state.gpr.__r[baseAddressIndex]; + + // Get immediateOffset from register (at index=2) + addressOffsetFromRegisterIndex = decodedInstruction.op[2].value; + addressOffsetFromRegister = m_state.gpr.__r[addressOffsetFromRegisterIndex]; + + break; + + case ARM_ADDR_LSWUB_SCALED: + case ARM_ADDR_LSWUB_SCALED_PRE: + case ARM_ADDR_LSWUB_SCALED_POST: + if (decodedInstruction.numOperands != 4) + { + DNBLogError("Expected 4 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); + return false; + } + + // Get baseAddress from register (at index=1) + baseAddressIndex = decodedInstruction.op[1].value; + baseAddress = m_state.gpr.__r[baseAddressIndex]; + + // Get immediateOffset from register (at index=2) + addressOffsetFromRegisterIndex = decodedInstruction.op[2].value; + addressOffsetFromRegister = m_state.gpr.__r[addressOffsetFromRegisterIndex]; + + // Get shiftAmount (at index=3) + shiftAmount = decodedInstruction.op[3].value; + + break; + + default: + break; + } + break; + + // 32b load multiple operations can load the PC along with everything else, + // usually to return from a function call + case ARM_INST_LDMDA: + case ARM_INST_LDMDB: + case ARM_INST_LDMIA: + case ARM_INST_LDMIB: + if (decodedInstruction.op[LDM_REGLIST].value & PC_REGLIST_BIT) + { + if (decodedInstruction.numOperands != 2) + { + DNBLogError("Expected 2 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); + return false; + } + + // Get baseAddress from register (at index=0) + baseAddressIndex = decodedInstruction.op[0].value; + baseAddress = m_state.gpr.__r[baseAddressIndex]; + + // Get registerList from register (at index=1) + registerList16 = (uint16_t)decodedInstruction.op[1].value; + + // Count number of registers to load in the multiple register list excluding the PC + registerList16NoPC = registerList16&0x3FFF; // exclude the PC + numRegistersToLoad=0; + for (int i = 0; i < 16; i++) + { + if (registerList16NoPC & 0x1) numRegistersToLoad++; + registerList16NoPC = registerList16NoPC >> 1; + } + } + else + { + DNBLogError("Destination register is not a PC! %s routine should be called on on instructions that modify the PC. Destination register is R%d!", __FUNCTION__, decodedInstruction.op[0].value); + return false; + } + break; + + // Normal 16-bit LD multiple can't touch R15, but POP can + case ARM_INST_POP: // Can also get the PC & updates SP + // Get baseAddress from SP (at index=0) + baseAddress = m_state.gpr.__sp; + + if (decodedInstruction.thumb16b) + { + // Get registerList from register (at index=0) + registerList8 = (uint8_t)decodedInstruction.op[0].value; + + // Count number of registers to load in the multiple register list + numRegistersToLoad=0; + for (int i = 0; i < 8; i++) + { + if (registerList8 & 0x1) numRegistersToLoad++; + registerList8 = registerList8 >> 1; + } + } + else + { + // Get registerList from register (at index=0) + registerList16 = (uint16_t)decodedInstruction.op[0].value; + + // Count number of registers to load in the multiple register list excluding the PC + registerList16NoPC = registerList16&0x3FFF; // exclude the PC + numRegistersToLoad=0; + for (int i = 0; i < 16; i++) + { + if (registerList16NoPC & 0x1) numRegistersToLoad++; + registerList16NoPC = registerList16NoPC >> 1; + } + } + break; + + // 16b TBB and TBH instructions load a jump address from a table + case ARM_INST_TBB: + case ARM_INST_TBH: + // Get baseAddress from register (at index=0) + baseAddressIndex = decodedInstruction.op[0].value; + baseAddress = m_state.gpr.__r[baseAddressIndex]; + + // Get immediateOffset from register (at index=1) + addressOffsetFromRegisterIndex = decodedInstruction.op[1].value; + addressOffsetFromRegister = m_state.gpr.__r[addressOffsetFromRegisterIndex]; + break; + + // ThumbEE branch-to-handler instructions: Jump to handlers at some offset + // from a special base pointer register (which is unknown at disassembly time) + case ARM_INST_HB: + case ARM_INST_HBP: +// TODO: ARM_INST_HB, ARM_INST_HBP + break; + + case ARM_INST_HBL: + case ARM_INST_HBLP: +// TODO: ARM_INST_HBL, ARM_INST_HBLP + break; + + // Breakpoint and software interrupt jump to interrupt handler (always ARM) + case ARM_INST_BKPT: + case ARM_INST_SMC: + case ARM_INST_SVC: + + // Return from exception, obviously modifies PC [interrupt only!] + case ARM_INST_RFEDA: + case ARM_INST_RFEDB: + case ARM_INST_RFEIA: + case ARM_INST_RFEIB: + + // Other instructions either can't change R15 or are "undefined" if you do, + // so no sane compiler should ever generate them & we don't care here. + // Also, R15 can only legally be used in a read-only manner for the + // various ARM addressing mode (to get PC-relative addressing of constants), + // but can NOT be used with any of the update modes. + default: + DNBLogError("%s should not be called for instruction code %d!", __FUNCTION__, decodedInstruction.instruction->code); + return false; + break; + } + + // Adjust PC if PC is one of the input operands + if (baseAddressIndex == PC_REG) + { + if (currentPCIsThumb) + baseAddress += 4; + else + baseAddress += 8; + } + + if (firstOperandIndex == PC_REG) + { + if (currentPCIsThumb) + firstOperand += 4; + else + firstOperand += 8; + } + + if (secondOperandIndex == PC_REG) + { + if (currentPCIsThumb) + secondOperand += 4; + else + secondOperand += 8; + } + + if (addressOffsetFromRegisterIndex == PC_REG) + { + if (currentPCIsThumb) + addressOffsetFromRegister += 4; + else + addressOffsetFromRegister += 8; + } + + DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, + "%s: firstOperand=%8.8x, secondOperand=%8.8x, immediateValue = %d, shiftAmount = %d, baseAddress = %8.8x, addressOffsetFromRegister = %8.8x, immediateOffset = %d, numRegistersToLoad = %d", + __FUNCTION__, + firstOperand, + secondOperand, + immediateValue, + shiftAmount, + baseAddress, + addressOffsetFromRegister, + immediateOffset, + numRegistersToLoad); + + + // Calculate following values after applying shiftAmount: + // - immediateOffsetAfterShift, secondOperandAfterShift + + switch (decodedInstruction.scaleMode) + { + case ARM_SCALE_NONE: + addressOffsetFromRegisterAfterShift = addressOffsetFromRegister; + secondOperandAfterShift = secondOperand; + break; + + case ARM_SCALE_LSL: // Logical shift left + addressOffsetFromRegisterAfterShift = addressOffsetFromRegister << shiftAmount; + secondOperandAfterShift = secondOperand << shiftAmount; + break; + + case ARM_SCALE_LSR: // Logical shift right + addressOffsetFromRegisterAfterShift = addressOffsetFromRegister >> shiftAmount; + secondOperandAfterShift = secondOperand >> shiftAmount; + break; + + case ARM_SCALE_ASR: // Arithmetic shift right + asm("mov %0, %1, asr %2" : "=r" (addressOffsetFromRegisterAfterShift) : "r" (addressOffsetFromRegister), "r" (shiftAmount)); + asm("mov %0, %1, asr %2" : "=r" (secondOperandAfterShift) : "r" (secondOperand), "r" (shiftAmount)); + break; + + case ARM_SCALE_ROR: // Rotate right + asm("mov %0, %1, ror %2" : "=r" (addressOffsetFromRegisterAfterShift) : "r" (addressOffsetFromRegister), "r" (shiftAmount)); + asm("mov %0, %1, ror %2" : "=r" (secondOperandAfterShift) : "r" (secondOperand), "r" (shiftAmount)); + break; + + case ARM_SCALE_RRX: // Rotate right, pulling in carry (1-bit shift only) + asm("mov %0, %1, rrx" : "=r" (addressOffsetFromRegisterAfterShift) : "r" (addressOffsetFromRegister)); + asm("mov %0, %1, rrx" : "=r" (secondOperandAfterShift) : "r" (secondOperand)); + break; + } + + // Emulate instruction to calculate targetPC + // All branches are already handled in the first switch statement. A branch should not reach this switch + switch (decodedInstruction.instruction->code) + { + // Arithmetic operations that can change the PC + case ARM_INST_ADC: + case ARM_INST_ADCS: + // Add with Carry + *targetPC = firstOperand + (secondOperandAfterShift + immediateValue) + cpsr_c; + break; + + case ARM_INST_ADD: + case ARM_INST_ADDS: + *targetPC = firstOperand + (secondOperandAfterShift + immediateValue); + break; + + case ARM_INST_AND: + case ARM_INST_ANDS: + *targetPC = firstOperand & (secondOperandAfterShift + immediateValue); + break; + + case ARM_INST_ASR: + case ARM_INST_ASRS: + asm("mov %0, %1, asr %2" : "=r" (myTargetPC) : "r" (firstOperand), "r" (secondOperandAfterShift + immediateValue)); + *targetPC = myTargetPC; + break; + + case ARM_INST_BIC: + case ARM_INST_BICS: + asm("bic %0, %1, %2" : "=r" (myTargetPC) : "r" (firstOperand), "r" (secondOperandAfterShift + immediateValue)); + *targetPC = myTargetPC; + break; + + case ARM_INST_EOR: + case ARM_INST_EORS: + asm("eor %0, %1, %2" : "=r" (myTargetPC) : "r" (firstOperand), "r" (secondOperandAfterShift + immediateValue)); + *targetPC = myTargetPC; + break; + + case ARM_INST_ORR: + case ARM_INST_ORRS: + asm("orr %0, %1, %2" : "=r" (myTargetPC) : "r" (firstOperand), "r" (secondOperandAfterShift + immediateValue)); + *targetPC = myTargetPC; + break; + + case ARM_INST_RSB: + case ARM_INST_RSBS: + asm("rsb %0, %1, %2" : "=r" (myTargetPC) : "r" (firstOperand), "r" (secondOperandAfterShift + immediateValue)); + *targetPC = myTargetPC; + break; + + case ARM_INST_RSC: + case ARM_INST_RSCS: + myTargetPC = secondOperandAfterShift - (firstOperand + !cpsr_c); + *targetPC = myTargetPC; + break; + + case ARM_INST_SBC: + case ARM_INST_SBCS: + asm("sbc %0, %1, %2" : "=r" (myTargetPC) : "r" (firstOperand), "r" (secondOperandAfterShift + immediateValue + !cpsr_c)); + *targetPC = myTargetPC; + break; + + case ARM_INST_SUB: + case ARM_INST_SUBS: + asm("sub %0, %1, %2" : "=r" (myTargetPC) : "r" (firstOperand), "r" (secondOperandAfterShift + immediateValue)); + *targetPC = myTargetPC; + break; + + // Logical shifts and move operations that can change the PC + case ARM_INST_LSL: + case ARM_INST_LSLS: + case ARM_INST_LSR: + case ARM_INST_LSRS: + case ARM_INST_MOV: + case ARM_INST_MOVS: + case ARM_INST_ROR: + case ARM_INST_RORS: + case ARM_INST_RRX: + case ARM_INST_RRXS: + myTargetPC = secondOperandAfterShift + immediateValue; + *targetPC = myTargetPC; + break; + + case ARM_INST_MVN: + case ARM_INST_MVNS: + myTargetPC = !(secondOperandAfterShift + immediateValue); + *targetPC = myTargetPC; + break; + + // Load register can be used to load PC, usually with a function pointer + case ARM_INST_LDR: + switch (decodedInstruction.addressMode) { + case ARM_ADDR_LSWUB_IMM_POST: + case ARM_ADDR_LSWUB_REG_POST: + case ARM_ADDR_LSWUB_SCALED_POST: + addressWherePCLives = baseAddress; + break; + + case ARM_ADDR_LSWUB_IMM: + case ARM_ADDR_LSWUB_REG: + case ARM_ADDR_LSWUB_SCALED: + case ARM_ADDR_LSWUB_IMM_PRE: + case ARM_ADDR_LSWUB_REG_PRE: + case ARM_ADDR_LSWUB_SCALED_PRE: + addressWherePCLives = baseAddress + (addressOffsetFromRegisterAfterShift + immediateOffset); + break; + + default: + break; + } + + mypid = m_thread->ProcessID(); + if (DNBProcessMemoryRead(mypid, addressWherePCLives, sizeof(nub_addr_t), &myTargetPC) != sizeof(nub_addr_t)) + { + DNBLogError("Could not read memory at %8.8x to get targetPC when processing the pop instruction!", addressWherePCLives); + return false; + } + + *targetPC = myTargetPC; + break; + + // 32b load multiple operations can load the PC along with everything else, + // usually to return from a function call + case ARM_INST_LDMDA: + mypid = m_thread->ProcessID(); + addressWherePCLives = baseAddress; + if (DNBProcessMemoryRead(mypid, addressWherePCLives, sizeof(nub_addr_t), &myTargetPC) != sizeof(nub_addr_t)) + { + DNBLogError("Could not read memory at %8.8x to get targetPC when processing the pop instruction!", addressWherePCLives); + return false; + } + + *targetPC = myTargetPC; + break; + + case ARM_INST_LDMDB: + mypid = m_thread->ProcessID(); + addressWherePCLives = baseAddress - 4; + if (DNBProcessMemoryRead(mypid, addressWherePCLives, sizeof(nub_addr_t), &myTargetPC) != sizeof(nub_addr_t)) + { + DNBLogError("Could not read memory at %8.8x to get targetPC when processing the pop instruction!", addressWherePCLives); + return false; + } + + *targetPC = myTargetPC; + break; + + case ARM_INST_LDMIB: + mypid = m_thread->ProcessID(); + addressWherePCLives = baseAddress + numRegistersToLoad*4 + 4; + if (DNBProcessMemoryRead(mypid, addressWherePCLives, sizeof(nub_addr_t), &myTargetPC) != sizeof(nub_addr_t)) + { + DNBLogError("Could not read memory at %8.8x to get targetPC when processing the pop instruction!", addressWherePCLives); + return false; + } + + *targetPC = myTargetPC; + break; + + case ARM_INST_LDMIA: // same as pop + // Normal 16-bit LD multiple can't touch R15, but POP can + case ARM_INST_POP: // Can also get the PC & updates SP + mypid = m_thread->ProcessID(); + addressWherePCLives = baseAddress + numRegistersToLoad*4; + if (DNBProcessMemoryRead(mypid, addressWherePCLives, sizeof(nub_addr_t), &myTargetPC) != sizeof(nub_addr_t)) + { + DNBLogError("Could not read memory at %8.8x to get targetPC when processing the pop instruction!", addressWherePCLives); + return false; + } + + *targetPC = myTargetPC; + break; + + // 16b TBB and TBH instructions load a jump address from a table + case ARM_INST_TBB: + mypid = m_thread->ProcessID(); + addressWherePCLives = baseAddress + addressOffsetFromRegisterAfterShift; + if (DNBProcessMemoryRead(mypid, addressWherePCLives, 1, &halfwords) != 1) + { + DNBLogError("Could not read memory at %8.8x to get targetPC when processing the TBB instruction!", addressWherePCLives); + return false; + } + // add 4 to currentPC since we are in Thumb mode and then add 2*halfwords + *targetPC = (currentPC + 4) + 2*halfwords; + break; + + case ARM_INST_TBH: + mypid = m_thread->ProcessID(); + addressWherePCLives = ((baseAddress + (addressOffsetFromRegisterAfterShift << 1)) & ~0x1); + if (DNBProcessMemoryRead(mypid, addressWherePCLives, 2, &halfwords) != 2) + { + DNBLogError("Could not read memory at %8.8x to get targetPC when processing the TBH instruction!", addressWherePCLives); + return false; + } + // add 4 to currentPC since we are in Thumb mode and then add 2*halfwords + *targetPC = (currentPC + 4) + 2*halfwords; + break; + + // ThumbEE branch-to-handler instructions: Jump to handlers at some offset + // from a special base pointer register (which is unknown at disassembly time) + case ARM_INST_HB: + case ARM_INST_HBP: + // TODO: ARM_INST_HB, ARM_INST_HBP + break; + + case ARM_INST_HBL: + case ARM_INST_HBLP: + // TODO: ARM_INST_HBL, ARM_INST_HBLP + break; + + // Breakpoint and software interrupt jump to interrupt handler (always ARM) + case ARM_INST_BKPT: + case ARM_INST_SMC: + case ARM_INST_SVC: + // TODO: ARM_INST_BKPT, ARM_INST_SMC, ARM_INST_SVC + break; + + // Return from exception, obviously modifies PC [interrupt only!] + case ARM_INST_RFEDA: + case ARM_INST_RFEDB: + case ARM_INST_RFEIA: + case ARM_INST_RFEIB: + // TODO: ARM_INST_RFEDA, ARM_INST_RFEDB, ARM_INST_RFEIA, ARM_INST_RFEIB + break; + + // Other instructions either can't change R15 or are "undefined" if you do, + // so no sane compiler should ever generate them & we don't care here. + // Also, R15 can only legally be used in a read-only manner for the + // various ARM addressing mode (to get PC-relative addressing of constants), + // but can NOT be used with any of the update modes. + default: + DNBLogError("%s should not be called for instruction code %d!", __FUNCTION__, decodedInstruction.instruction->code); + return false; + break; + } + + return true; +} + +void +DNBArchMachARM::EvaluateNextInstructionForSoftwareBreakpointSetup(nub_addr_t currentPC, uint32_t cpsr, bool currentPCIsThumb, nub_addr_t *nextPC, bool *nextPCIsThumb) +{ + DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "DNBArchMachARM::EvaluateNextInstructionForSoftwareBreakpointSetup() called"); + + nub_addr_t targetPC = INVALID_NUB_ADDRESS; + uint32_t registerValue; + arm_error_t decodeError; + nub_addr_t currentPCInITBlock, nextPCInITBlock; + int i; + bool last_decoded_instruction_executes = true; + + DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: default nextPC=0x%8.8x (%s)", __FUNCTION__, *nextPC, *nextPCIsThumb ? "Thumb" : "ARM"); + + // Update *nextPC and *nextPCIsThumb for special cases + if (m_last_decode_thumb.itBlockRemaining) // we are in an IT block + { + // Set the nextPC to the PC of the instruction which will execute in the IT block + // If none of the instruction execute in the IT block based on the condition flags, + // then point to the instruction immediately following the IT block + const int itBlockRemaining = m_last_decode_thumb.itBlockRemaining; + DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: itBlockRemaining=%8.8x", __FUNCTION__, itBlockRemaining); + + // Determine the PC at which the next instruction resides + if (m_last_decode_arm.thumb16b) + currentPCInITBlock = currentPC + 2; + else + currentPCInITBlock = currentPC + 4; + + for (i = 0; i < itBlockRemaining; i++) + { + DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: currentPCInITBlock=%8.8x", __FUNCTION__, currentPCInITBlock); + decodeError = DecodeInstructionUsingDisassembler(currentPCInITBlock, cpsr, &m_last_decode_arm, &m_last_decode_thumb, &nextPCInITBlock); + + if (decodeError != ARM_SUCCESS) + DNBLogError("unable to disassemble instruction at 0x%8.8lx", currentPCInITBlock); + + DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: condition=%d", __FUNCTION__, m_last_decode_arm.condition); + if (ConditionPassed(m_last_decode_arm.condition, cpsr)) + { + DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: Condition codes matched for instruction %d", __FUNCTION__, i); + break; // break from the for loop + } + else + { + DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: Condition codes DID NOT matched for instruction %d", __FUNCTION__, i); + } + + // update currentPC and nextPCInITBlock + currentPCInITBlock = nextPCInITBlock; + } + + if (i == itBlockRemaining) // We came out of the IT block without executing any instructions + last_decoded_instruction_executes = false; + + *nextPC = currentPCInITBlock; + DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: After IT block step-through: *nextPC=%8.8x", __FUNCTION__, *nextPC); + } + + DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, + "%s: cpsr = %8.8x, thumb16b = %d, thumb = %d, branch = %d, conditional = %d, knownTarget = %d, links = %d, canSwitchMode = %d, doesSwitchMode = %d", + __FUNCTION__, + cpsr, + m_last_decode_arm.thumb16b, + m_last_decode_arm.thumb, + m_last_decode_arm.branch, + m_last_decode_arm.conditional, + m_last_decode_arm.knownTarget, + m_last_decode_arm.links, + m_last_decode_arm.canSwitchMode, + m_last_decode_arm.doesSwitchMode); + + + if (last_decoded_instruction_executes && // Was this a conditional instruction that did execute? + m_last_decode_arm.branch && // Can this instruction change the PC? + (m_last_decode_arm.instruction->code != ARM_INST_SVC)) // If this instruction is not an SVC instruction + { + // Set targetPC. Compute if needed. + if (m_last_decode_arm.knownTarget) + { + // Fixed, known PC-relative + targetPC = m_last_decode_arm.targetPC; + } + else + { + // if targetPC is not known at compile time (PC-relative target), compute targetPC + if (!ComputeNextPC(currentPC, m_last_decode_arm, currentPCIsThumb, &targetPC)) + { + DNBLogError("%s: Unable to compute targetPC for instruction at 0x%8.8lx", __FUNCTION__, currentPC); + targetPC = INVALID_NUB_ADDRESS; + } + } + + DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: targetPC=0x%8.8x, cpsr=0x%8.8x, condition=0x%hhx", __FUNCTION__, targetPC, cpsr, m_last_decode_arm.condition); + + // Refine nextPC computation + if ((m_last_decode_arm.instruction->code == ARM_INST_CBZ) || + (m_last_decode_arm.instruction->code == ARM_INST_CBNZ)) + { + // Compare and branch on zero/non-zero (Thumb-16 only) + // Unusual condition check built into the instruction + registerValue = m_state.gpr.__r[m_last_decode_arm.op[REG_RD].value]; + + if (m_last_decode_arm.instruction->code == ARM_INST_CBZ) + { + if (registerValue == 0) + *nextPC = targetPC; + } + else + { + if (registerValue != 0) + *nextPC = targetPC; + } + } + else if (m_last_decode_arm.conditional) // Is the change conditional on flag results? + { + if (ConditionPassed(m_last_decode_arm.condition, cpsr)) // conditions match + { + DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: Condition matched!", __FUNCTION__); + *nextPC = targetPC; + } + else + { + DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: Condition did not match!", __FUNCTION__); + } + } + else + { + *nextPC = targetPC; + } + + // Refine nextPCIsThumb computation + if (m_last_decode_arm.doesSwitchMode) + { + *nextPCIsThumb = !currentPCIsThumb; + } + else if (m_last_decode_arm.canSwitchMode) + { + // Legal to switch ARM <--> Thumb mode with this branch + // dependent on bit[0] of targetPC + *nextPCIsThumb = (*nextPC & 1u) != 0; + } + else + { + *nextPCIsThumb = currentPCIsThumb; + } + } + + DNBLogThreadedIf(LOG_STEP, "%s: calculated nextPC=0x%8.8x (%s)", __FUNCTION__, *nextPC, *nextPCIsThumb ? "Thumb" : "ARM"); +} + + +arm_error_t +DNBArchMachARM::DecodeInstructionUsingDisassembler(nub_addr_t curr_pc, uint32_t curr_cpsr, arm_decoded_instruction_t *decodedInstruction, thumb_static_data_t *thumbStaticData, nub_addr_t *next_pc) +{ + + DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: pc=0x%8.8x, cpsr=0x%8.8x", __FUNCTION__, curr_pc, curr_cpsr); + + const uint32_t isetstate_mask = MASK_CPSR_T | MASK_CPSR_J; + const uint32_t curr_isetstate = curr_cpsr & isetstate_mask; + uint32_t opcode32; + nub_addr_t nextPC = curr_pc; + arm_error_t decodeReturnCode = ARM_SUCCESS; + + m_last_decode_pc = curr_pc; + DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: last_decode_pc=0x%8.8x", __FUNCTION__, m_last_decode_pc); + + switch (curr_isetstate) { + case 0x0: // ARM Instruction + // Read the ARM opcode + if (m_thread->Process()->Task().ReadMemory(curr_pc, 4, &opcode32) != 4) + { + DNBLogError("unable to read opcode bits 31:0 for an ARM opcode at 0x%8.8lx", curr_pc); + decodeReturnCode = ARM_ERROR; + } + else + { + nextPC += 4; + decodeReturnCode = ArmDisassembler((uint64_t)curr_pc, opcode32, false, decodedInstruction, NULL, 0, NULL, 0); + + if (decodeReturnCode != ARM_SUCCESS) + DNBLogError("Unable to decode ARM instruction 0x%8.8x at 0x%8.8lx", opcode32, curr_pc); + } + break; + + case 0x20: // Thumb Instruction + uint16_t opcode16; + // Read the a 16 bit Thumb opcode + if (m_thread->Process()->Task().ReadMemory(curr_pc, 2, &opcode16) != 2) + { + DNBLogError("unable to read opcode bits 15:0 for a thumb opcode at 0x%8.8lx", curr_pc); + decodeReturnCode = ARM_ERROR; + } + else + { + nextPC += 2; + opcode32 = opcode16; + + decodeReturnCode = ThumbDisassembler((uint64_t)curr_pc, opcode16, false, false, thumbStaticData, decodedInstruction, NULL, 0, NULL, 0); + + switch (decodeReturnCode) { + case ARM_SKIP: + // 32 bit thumb opcode + nextPC += 2; + if (m_thread->Process()->Task().ReadMemory(curr_pc+2, 2, &opcode16) != 2) + { + DNBLogError("unable to read opcode bits 15:0 for a thumb opcode at 0x%8.8lx", curr_pc+2); + } + else + { + opcode32 = (opcode32 << 16) | opcode16; + + decodeReturnCode = ThumbDisassembler((uint64_t)(curr_pc+2), opcode16, false, false, thumbStaticData, decodedInstruction, NULL, 0, NULL, 0); + + if (decodeReturnCode != ARM_SUCCESS) + DNBLogError("Unable to decode 2nd half of Thumb instruction 0x%8.4hx at 0x%8.8lx", opcode16, curr_pc+2); + break; + } + break; + + case ARM_SUCCESS: + // 16 bit thumb opcode; at this point we are done decoding the opcode + break; + + default: + DNBLogError("Unable to decode Thumb instruction 0x%8.4hx at 0x%8.8lx", opcode16, curr_pc); + decodeReturnCode = ARM_ERROR; + break; + } + } + break; + + default: + break; + } + + if (next_pc) + *next_pc = nextPC; + + return decodeReturnCode; +} + +nub_bool_t +DNBArchMachARM::BreakpointHit(nub_process_t pid, nub_thread_t tid, nub_break_t breakID, void *baton) +{ + nub_addr_t bkpt_pc = (nub_addr_t)baton; + DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s(pid = %i, tid = %4.4x, breakID = %u, baton = %p): Setting PC to 0x%8.8x", __FUNCTION__, pid, tid, breakID, baton, bkpt_pc); + return DNBThreadSetRegisterValueByID(pid, tid, REGISTER_SET_GENERIC, GENERIC_REGNUM_PC, bkpt_pc); +} + +// Set the single step bit in the processor status register. +kern_return_t +DNBArchMachARM::SetSingleStepSoftwareBreakpoints() +{ + DNBError err; + err = GetGPRState(false); + + if (err.Fail()) + { + err.LogThreaded("%s: failed to read the GPR registers", __FUNCTION__); + return err.Error(); + } + + nub_addr_t curr_pc = m_state.gpr.__pc; + uint32_t curr_cpsr = m_state.gpr.__cpsr; + nub_addr_t next_pc = curr_pc; + + bool curr_pc_is_thumb = (m_state.gpr.__cpsr & 0x20) != 0; + bool next_pc_is_thumb = curr_pc_is_thumb; + + uint32_t curr_itstate = ((curr_cpsr & 0x6000000) >> 25) | ((curr_cpsr & 0xFC00) >> 8); + bool inITBlock = (curr_itstate & 0xF) ? 1 : 0; + bool lastInITBlock = ((curr_itstate & 0xF) == 0x8) ? 1 : 0; + + DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: curr_pc=0x%8.8x (%s), curr_itstate=0x%x, inITBlock=%d, lastInITBlock=%d", __FUNCTION__, curr_pc, curr_pc_is_thumb ? "Thumb" : "ARM", curr_itstate, inITBlock, lastInITBlock); + + // If the instruction is not in the IT block, then decode using the Disassembler and compute next_pc + if (!inITBlock) + { + DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: Decoding an instruction NOT in the IT block", __FUNCTION__); + + arm_error_t decodeReturnCode = DecodeInstructionUsingDisassembler(curr_pc, curr_cpsr, &m_last_decode_arm, &m_last_decode_thumb, &next_pc); + + if (decodeReturnCode != ARM_SUCCESS) + { + err = KERN_INVALID_ARGUMENT; + DNBLogError("DNBArchMachARM::SetSingleStepSoftwareBreakpoints: Unable to disassemble instruction at 0x%8.8lx", curr_pc); + } + } + else + { + next_pc = curr_pc + ((m_last_decode_arm.thumb16b) ? 2 : 4); + } + + // Instruction is NOT in the IT block OR + if (!inITBlock) + { + DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: normal instruction", __FUNCTION__); + EvaluateNextInstructionForSoftwareBreakpointSetup(curr_pc, m_state.gpr.__cpsr, curr_pc_is_thumb, &next_pc, &next_pc_is_thumb); + } + else if (inITBlock && !m_last_decode_arm.setsFlags) + { + DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: IT instruction that doesn't set flags", __FUNCTION__); + EvaluateNextInstructionForSoftwareBreakpointSetup(curr_pc, m_state.gpr.__cpsr, curr_pc_is_thumb, &next_pc, &next_pc_is_thumb); + } + else if (lastInITBlock && m_last_decode_arm.branch) + { + DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: IT instruction which last in the IT block and is a branch", __FUNCTION__); + EvaluateNextInstructionForSoftwareBreakpointSetup(curr_pc, m_state.gpr.__cpsr, curr_pc_is_thumb, &next_pc, &next_pc_is_thumb); + } + else + { + // Instruction is in IT block and can modify the CPSR flags + DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: IT instruction that sets flags", __FUNCTION__); + + // NOTE: When this point of code is reached, the instruction at curr_pc has already been decoded + // inside the function ThreadDidStop(). Therefore m_last_decode_arm, m_last_decode_thumb + // reflect the decoded instruction at curr_pc + + // If we find an instruction inside the IT block which will set/modify the condition flags (NZCV bits in CPSR), + // we set breakpoints at all remaining instructions inside the IT block starting from the instruction immediately + // following this one AND a breakpoint at the instruction immediately following the IT block. We do this because + // we cannot determine the next_pc until the instruction at which we are currently stopped executes. Hence we + // insert (m_last_decode_thumb.itBlockRemaining+1) 16-bit Thumb breakpoints at consecutive memory locations + // starting at addrOfNextInstructionInITBlock. We record these breakpoints in class variable m_sw_single_step_itblock_break_id[], + // and also record the total number of IT breakpoints set in the variable 'm_sw_single_step_itblock_break_count'. + + // The instructions inside the IT block, which are replaced by the 16-bit Thumb breakpoints (opcode=0xDEFE) + // instructions, can be either Thumb-16 or Thumb-32. When a Thumb-32 instruction (say, inst#1) is replaced Thumb + // by a 16-bit breakpoint (OS only supports 16-bit breakpoints in Thumb mode and 32-bit breakpoints in ARM mode), the + // breakpoint for the next instruction (say instr#2) is saved in the upper half of this Thumb-32 (instr#1) + // instruction. Hence if the execution stops at Breakpoint2 corresponding to instr#2, the PC is offset by 16-bits. + // We therefore have to keep track of PC of each instruction in the IT block that is being replaced with the 16-bit + // Thumb breakpoint, to ensure that when the breakpoint is hit, the PC is adjusted to the correct value. We save + // the actual PC corresponding to each instruction in the IT block by associating a call back with each breakpoint + // we set and passing it as a baton. When the breakpoint hits and the callback routine is called, the routine + // adjusts the PC based on the baton that is passed to it. + + nub_addr_t addrOfNextInstructionInITBlock, pcInITBlock, nextPCInITBlock, bpAddressInITBlock; + uint16_t opcode16; + uint32_t opcode32; + + addrOfNextInstructionInITBlock = (m_last_decode_arm.thumb16b) ? curr_pc + 2 : curr_pc + 4; + + pcInITBlock = addrOfNextInstructionInITBlock; + + DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: itBlockRemaining=%d", __FUNCTION__, m_last_decode_thumb.itBlockRemaining); + + m_sw_single_step_itblock_break_count = 0; + for (int i = 0; i <= m_last_decode_thumb.itBlockRemaining; i++) + { + if (NUB_BREAK_ID_IS_VALID(m_sw_single_step_itblock_break_id[i])) + { + DNBLogError("FunctionProfiler::SetSingleStepSoftwareBreakpoints(): Array m_sw_single_step_itblock_break_id should not contain any valid breakpoint IDs at this point. But found a valid breakID=%d at index=%d", m_sw_single_step_itblock_break_id[i], i); + } + else + { + nextPCInITBlock = pcInITBlock; + // Compute nextPCInITBlock based on opcode present at pcInITBlock + if (m_thread->Process()->Task().ReadMemory(pcInITBlock, 2, &opcode16) == 2) + { + opcode32 = opcode16; + nextPCInITBlock += 2; + + // Check for 32 bit thumb opcode and read the upper 16 bits if needed + if (((opcode32 & 0xE000) == 0xE000) && (opcode32 & 0x1800)) + { + // Adjust 'next_pc_in_itblock' to point to the default next Thumb instruction for + // a 32 bit Thumb opcode + // Read bits 31:16 of a 32 bit Thumb opcode + if (m_thread->Process()->Task().ReadMemory(pcInITBlock+2, 2, &opcode16) == 2) + { + // 32 bit thumb opcode + opcode32 = (opcode32 << 16) | opcode16; + nextPCInITBlock += 2; + } + else + { + DNBLogError("FunctionProfiler::SetSingleStepSoftwareBreakpoints(): Unable to read opcode bits 31:16 for a 32 bit thumb opcode at pc=0x%8.8lx", nextPCInITBlock); + } + } + } + else + { + DNBLogError("FunctionProfiler::SetSingleStepSoftwareBreakpoints(): Error reading 16-bit Thumb instruction at pc=0x%8.8x", nextPCInITBlock); + } + + + // Set breakpoint and associate a callback function with it + bpAddressInITBlock = addrOfNextInstructionInITBlock + 2*i; + DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: Setting IT breakpoint[%d] at address: 0x%8.8x", __FUNCTION__, i, bpAddressInITBlock); + + m_sw_single_step_itblock_break_id[i] = m_thread->Process()->CreateBreakpoint(bpAddressInITBlock, 2, false, m_thread->ThreadID()); + if (!NUB_BREAK_ID_IS_VALID(m_sw_single_step_itblock_break_id[i])) + err = KERN_INVALID_ARGUMENT; + else + { + DNBLogThreadedIf(LOG_STEP, "%s: Set IT breakpoint[%i]=%d set at 0x%8.8x for instruction at 0x%8.8x", __FUNCTION__, i, m_sw_single_step_itblock_break_id[i], bpAddressInITBlock, pcInITBlock); + + // Set the breakpoint callback for these special IT breakpoints + // so that if one of these breakpoints gets hit, it knows to + // update the PC to the original address of the conditional + // IT instruction. + DNBBreakpointSetCallback(m_thread->ProcessID(), m_sw_single_step_itblock_break_id[i], DNBArchMachARM::BreakpointHit, (void*)pcInITBlock); + m_sw_single_step_itblock_break_count++; + } + } + + pcInITBlock = nextPCInITBlock; + } + + DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: Set %u IT software single breakpoints.", __FUNCTION__, m_sw_single_step_itblock_break_count); + + } + + DNBLogThreadedIf(LOG_STEP, "%s: next_pc=0x%8.8x (%s)", __FUNCTION__, next_pc, next_pc_is_thumb ? "Thumb" : "ARM"); + + if (next_pc & 0x1) + { + assert(next_pc_is_thumb); + } + + if (next_pc_is_thumb) + { + next_pc &= ~0x1; + } + else + { + assert((next_pc & 0x3) == 0); + } + + if (!inITBlock || (inITBlock && !m_last_decode_arm.setsFlags) || (lastInITBlock && m_last_decode_arm.branch)) + { + err = KERN_SUCCESS; + +#if defined DNB_ARCH_MACH_ARM_DEBUG_SW_STEP + m_sw_single_step_next_pc = next_pc; + if (next_pc_is_thumb) + m_sw_single_step_next_pc |= 1; // Set bit zero if the next PC is expected to be Thumb +#else + const DNBBreakpoint *bp = m_thread->Process()->Breakpoints().FindByAddress(next_pc); + + if (bp == NULL) + { + m_sw_single_step_break_id = m_thread->Process()->CreateBreakpoint(next_pc, next_pc_is_thumb ? 2 : 4, false, m_thread->ThreadID()); + if (!NUB_BREAK_ID_IS_VALID(m_sw_single_step_break_id)) + err = KERN_INVALID_ARGUMENT; + DNBLogThreadedIf(LOG_STEP, "%s: software single step breakpoint with breakID=%d set at 0x%8.8x", __FUNCTION__, m_sw_single_step_break_id, next_pc); + } +#endif + } + + return err.Error(); +} + +uint32_t +DNBArchMachARM::NumSupportedHardwareBreakpoints() +{ + // Set the init value to something that will let us know that we need to + // autodetect how many breakpoints are supported dynamically... + static uint32_t g_num_supported_hw_breakpoints = UINT_MAX; + if (g_num_supported_hw_breakpoints == UINT_MAX) + { + // Set this to zero in case we can't tell if there are any HW breakpoints + g_num_supported_hw_breakpoints = 0; + + // Read the DBGDIDR to get the number of available hardware breakpoints + // However, in some of our current armv7 processors, hardware + // breakpoints/watchpoints were not properly connected. So detect those + // cases using a field in a sysctl. For now we are using "hw.cpusubtype" + // field to distinguish CPU architectures. This is a hack until we can + // get <rdar://problem/6372672> fixed, at which point we will switch to + // using a different sysctl string that will tell us how many BRPs + // are available to us directly without having to read DBGDIDR. + uint32_t register_DBGDIDR; + + asm("mrc p14, 0, %0, c0, c0, 0" : "=r" (register_DBGDIDR)); + uint32_t numBRPs = bits(register_DBGDIDR, 27, 24); + // Zero is reserved for the BRP count, so don't increment it if it is zero + if (numBRPs > 0) + numBRPs++; + DNBLogThreadedIf(LOG_THREAD, "DBGDIDR=0x%8.8x (number BRP pairs = %u)", register_DBGDIDR, numBRPs); + + if (numBRPs > 0) + { + uint32_t cpusubtype; + size_t len; + len = sizeof(cpusubtype); + // TODO: remove this hack and change to using hw.optional.xx when implmented + if (::sysctlbyname("hw.cpusubtype", &cpusubtype, &len, NULL, 0) == 0) + { + DNBLogThreadedIf(LOG_THREAD, "hw.cpusubtype=0x%d", cpusubtype); + if (cpusubtype == CPU_SUBTYPE_ARM_V7) + DNBLogThreadedIf(LOG_THREAD, "Hardware breakpoints disabled for armv7 (rdar://problem/6372672)"); + else + g_num_supported_hw_breakpoints = numBRPs; + } + } + + } + return g_num_supported_hw_breakpoints; +} + + +uint32_t +DNBArchMachARM::NumSupportedHardwareWatchpoints() +{ + // Set the init value to something that will let us know that we need to + // autodetect how many watchpoints are supported dynamically... + static uint32_t g_num_supported_hw_watchpoints = UINT_MAX; + if (g_num_supported_hw_watchpoints == UINT_MAX) + { + // Set this to zero in case we can't tell if there are any HW breakpoints + g_num_supported_hw_watchpoints = 0; + // Read the DBGDIDR to get the number of available hardware breakpoints + // However, in some of our current armv7 processors, hardware + // breakpoints/watchpoints were not properly connected. So detect those + // cases using a field in a sysctl. For now we are using "hw.cpusubtype" + // field to distinguish CPU architectures. This is a hack until we can + // get <rdar://problem/6372672> fixed, at which point we will switch to + // using a different sysctl string that will tell us how many WRPs + // are available to us directly without having to read DBGDIDR. + + uint32_t register_DBGDIDR; + asm("mrc p14, 0, %0, c0, c0, 0" : "=r" (register_DBGDIDR)); + uint32_t numWRPs = bits(register_DBGDIDR, 31, 28) + 1; + DNBLogThreadedIf(LOG_THREAD, "DBGDIDR=0x%8.8x (number WRP pairs = %u)", register_DBGDIDR, numWRPs); + + if (numWRPs > 0) + { + uint32_t cpusubtype; + size_t len; + len = sizeof(cpusubtype); + // TODO: remove this hack and change to using hw.optional.xx when implmented + if (::sysctlbyname("hw.cpusubtype", &cpusubtype, &len, NULL, 0) == 0) + { + DNBLogThreadedIf(LOG_THREAD, "hw.cpusubtype=0x%d", cpusubtype); + + if (cpusubtype == CPU_SUBTYPE_ARM_V7) + DNBLogThreadedIf(LOG_THREAD, "Hardware watchpoints disabled for armv7 (rdar://problem/6372672)"); + else + g_num_supported_hw_watchpoints = numWRPs; + } + } + + } + return g_num_supported_hw_watchpoints; +} + + +uint32_t +DNBArchMachARM::EnableHardwareBreakpoint (nub_addr_t addr, nub_size_t size) +{ + // Make sure our address isn't bogus + if (addr & 1) + return INVALID_NUB_HW_INDEX; + + kern_return_t kret = GetDBGState(false); + + if (kret == KERN_SUCCESS) + { + const uint32_t num_hw_breakpoints = NumSupportedHardwareBreakpoints(); + uint32_t i; + for (i=0; i<num_hw_breakpoints; ++i) + { + if ((m_state.dbg.__bcr[i] & BCR_ENABLE) == 0) + break; // We found an available hw breakpoint slot (in i) + } + + // See if we found an available hw breakpoint slot above + if (i < num_hw_breakpoints) + { + // Make sure bits 1:0 are clear in our address + m_state.dbg.__bvr[i] = addr & ~((nub_addr_t)3); + + if (size == 2 || addr & 2) + { + uint32_t byte_addr_select = (addr & 2) ? BAS_IMVA_2_3 : BAS_IMVA_0_1; + + // We have a thumb breakpoint + // We have an ARM breakpoint + m_state.dbg.__bcr[i] = BCR_M_IMVA_MATCH | // Stop on address mismatch + byte_addr_select | // Set the correct byte address select so we only trigger on the correct opcode + S_USER | // Which modes should this breakpoint stop in? + BCR_ENABLE; // Enable this hardware breakpoint + DNBLogThreadedIf(LOG_BREAKPOINTS, "DNBArchMachARM::EnableHardwareBreakpoint( addr = %8.8p, size = %u ) - BVR%u/BCR%u = 0x%8.8x / 0x%8.8x (Thumb)", + addr, + size, + i, + i, + m_state.dbg.__bvr[i], + m_state.dbg.__bcr[i]); + } + else if (size == 4) + { + // We have an ARM breakpoint + m_state.dbg.__bcr[i] = BCR_M_IMVA_MATCH | // Stop on address mismatch + BAS_IMVA_ALL | // Stop on any of the four bytes following the IMVA + S_USER | // Which modes should this breakpoint stop in? + BCR_ENABLE; // Enable this hardware breakpoint + DNBLogThreadedIf(LOG_BREAKPOINTS, "DNBArchMachARM::EnableHardwareBreakpoint( addr = %8.8p, size = %u ) - BVR%u/BCR%u = 0x%8.8x / 0x%8.8x (ARM)", + addr, + size, + i, + i, + m_state.dbg.__bvr[i], + m_state.dbg.__bcr[i]); + } + + kret = SetDBGState(); + DNBLogThreadedIf(LOG_BREAKPOINTS, "DNBArchMachARM::EnableHardwareBreakpoint() SetDBGState() => 0x%8.8x.", kret); + + if (kret == KERN_SUCCESS) + return i; + } + else + { + DNBLogThreadedIf(LOG_BREAKPOINTS, "DNBArchMachARM::EnableHardwareBreakpoint(addr = %8.8p, size = %u) => all hardware breakpoint resources are being used.", addr, size); + } + } + + return INVALID_NUB_HW_INDEX; +} + +bool +DNBArchMachARM::DisableHardwareBreakpoint (uint32_t hw_index) +{ + kern_return_t kret = GetDBGState(false); + + const uint32_t num_hw_points = NumSupportedHardwareBreakpoints(); + if (kret == KERN_SUCCESS) + { + if (hw_index < num_hw_points) + { + m_state.dbg.__bcr[hw_index] = 0; + DNBLogThreadedIf(LOG_BREAKPOINTS, "DNBArchMachARM::SetHardwareBreakpoint( %u ) - BVR%u = 0x%8.8x BCR%u = 0x%8.8x", + hw_index, + hw_index, + m_state.dbg.__bvr[hw_index], + hw_index, + m_state.dbg.__bcr[hw_index]); + + kret = SetDBGState(); + + if (kret == KERN_SUCCESS) + return true; + } + } + return false; +} + +uint32_t +DNBArchMachARM::EnableHardwareWatchpoint (nub_addr_t addr, nub_size_t size, bool read, bool write) +{ + DNBLogThreadedIf(LOG_WATCHPOINTS, "DNBArchMachARM::EnableHardwareWatchpoint(addr = %8.8p, size = %u, read = %u, write = %u)", addr, size, read, write); + + const uint32_t num_hw_watchpoints = NumSupportedHardwareWatchpoints(); + + // Can't watch zero bytes + if (size == 0) + return INVALID_NUB_HW_INDEX; + + // We must watch for either read or write + if (read == false && write == false) + return INVALID_NUB_HW_INDEX; + + // Can't watch more than 4 bytes per WVR/WCR pair + if (size > 4) + return INVALID_NUB_HW_INDEX; + + // We can only watch up to four bytes that follow a 4 byte aligned address + // per watchpoint register pair. Since we have at most so we can only watch + // until the next 4 byte boundary and we need to make sure we can properly + // encode this. + uint32_t addr_word_offset = addr % 4; + DNBLogThreadedIf(LOG_WATCHPOINTS, "DNBArchMachARM::EnableHardwareWatchpoint() - addr_word_offset = 0x%8.8x", addr_word_offset); + + uint32_t byte_mask = ((1u << size) - 1u) << addr_word_offset; + DNBLogThreadedIf(LOG_WATCHPOINTS, "DNBArchMachARM::EnableHardwareWatchpoint() - byte_mask = 0x%8.8x", byte_mask); + if (byte_mask > 0xfu) + return INVALID_NUB_HW_INDEX; + + // Read the debug state + kern_return_t kret = GetDBGState(false); + + if (kret == KERN_SUCCESS) + { + // Check to make sure we have the needed hardware support + uint32_t i = 0; + + for (i=0; i<num_hw_watchpoints; ++i) + { + if ((m_state.dbg.__wcr[i] & WCR_ENABLE) == 0) + break; // We found an available hw breakpoint slot (in i) + } + + // See if we found an available hw breakpoint slot above + if (i < num_hw_watchpoints) + { + // Make the byte_mask into a valid Byte Address Select mask + uint32_t byte_address_select = byte_mask << 5; + // Make sure bits 1:0 are clear in our address + m_state.dbg.__wvr[i] = addr & ~((nub_addr_t)3); + m_state.dbg.__wcr[i] = byte_address_select | // Which bytes that follow the IMVA that we will watch + S_USER | // Stop only in user mode + (read ? WCR_LOAD : 0) | // Stop on read access? + (write ? WCR_STORE : 0) | // Stop on write access? + WCR_ENABLE; // Enable this watchpoint; + + kret = SetDBGState(); + DNBLogThreadedIf(LOG_WATCHPOINTS, "DNBArchMachARM::EnableHardwareWatchpoint() SetDBGState() => 0x%8.8x.", kret); + + if (kret == KERN_SUCCESS) + return i; + } + else + { + DNBLogThreadedIf(LOG_WATCHPOINTS, "DNBArchMachARM::EnableHardwareWatchpoint(): All hardware resources (%u) are in use.", num_hw_watchpoints); + } + } + return INVALID_NUB_HW_INDEX; +} + +bool +DNBArchMachARM::DisableHardwareWatchpoint (uint32_t hw_index) +{ + kern_return_t kret = GetDBGState(false); + + const uint32_t num_hw_points = NumSupportedHardwareWatchpoints(); + if (kret == KERN_SUCCESS) + { + if (hw_index < num_hw_points) + { + m_state.dbg.__wcr[hw_index] = 0; + DNBLogThreadedIf(LOG_WATCHPOINTS, "DNBArchMachARM::ClearHardwareWatchpoint( %u ) - WVR%u = 0x%8.8x WCR%u = 0x%8.8x", + hw_index, + hw_index, + m_state.dbg.__wvr[hw_index], + hw_index, + m_state.dbg.__wcr[hw_index]); + + kret = SetDBGState(); + + if (kret == KERN_SUCCESS) + return true; + } + } + return false; +} + +//---------------------------------------------------------------------- +// Register information defintions for 32 bit ARMV6. +//---------------------------------------------------------------------- +enum gpr_regnums +{ + e_regNumGPR_r0 = 0, + e_regNumGPR_r1, + e_regNumGPR_r2, + e_regNumGPR_r3, + e_regNumGPR_r4, + e_regNumGPR_r5, + e_regNumGPR_r6, + e_regNumGPR_r7, + e_regNumGPR_r8, + e_regNumGPR_r9, + e_regNumGPR_r10, + e_regNumGPR_r11, + e_regNumGPR_r12, + e_regNumGPR_sp, + e_regNumGPR_lr, + e_regNumGPR_pc, + e_regNumGPR_cpsr +}; + +// General purpose registers +static DNBRegisterInfo g_gpr_registers[] = +{ + { "r0" , Uint, 4, Hex }, + { "r1" , Uint, 4, Hex }, + { "r2" , Uint, 4, Hex }, + { "r3" , Uint, 4, Hex }, + { "r4" , Uint, 4, Hex }, + { "r5" , Uint, 4, Hex }, + { "r6" , Uint, 4, Hex }, + { "r7" , Uint, 4, Hex }, + { "r8" , Uint, 4, Hex }, + { "r9" , Uint, 4, Hex }, + { "r10" , Uint, 4, Hex }, + { "r11" , Uint, 4, Hex }, + { "r12" , Uint, 4, Hex }, + { "sp" , Uint, 4, Hex }, + { "lr" , Uint, 4, Hex }, + { "pc" , Uint, 4, Hex }, + { "cpsr" , Uint, 4, Hex }, +}; + +// Floating point registers +static DNBRegisterInfo g_vfp_registers[] = +{ + { "s0" , IEEE754, 4, Float }, + { "s1" , IEEE754, 4, Float }, + { "s2" , IEEE754, 4, Float }, + { "s3" , IEEE754, 4, Float }, + { "s4" , IEEE754, 4, Float }, + { "s5" , IEEE754, 4, Float }, + { "s6" , IEEE754, 4, Float }, + { "s7" , IEEE754, 4, Float }, + { "s8" , IEEE754, 4, Float }, + { "s9" , IEEE754, 4, Float }, + { "s10" , IEEE754, 4, Float }, + { "s11" , IEEE754, 4, Float }, + { "s12" , IEEE754, 4, Float }, + { "s13" , IEEE754, 4, Float }, + { "s14" , IEEE754, 4, Float }, + { "s15" , IEEE754, 4, Float }, + { "s16" , IEEE754, 4, Float }, + { "s17" , IEEE754, 4, Float }, + { "s18" , IEEE754, 4, Float }, + { "s19" , IEEE754, 4, Float }, + { "s20" , IEEE754, 4, Float }, + { "s21" , IEEE754, 4, Float }, + { "s22" , IEEE754, 4, Float }, + { "s23" , IEEE754, 4, Float }, + { "s24" , IEEE754, 4, Float }, + { "s25" , IEEE754, 4, Float }, + { "s26" , IEEE754, 4, Float }, + { "s27" , IEEE754, 4, Float }, + { "s28" , IEEE754, 4, Float }, + { "s29" , IEEE754, 4, Float }, + { "s30" , IEEE754, 4, Float }, + { "s31" , IEEE754, 4, Float }, + { "fpscr" , Uint, 4, Hex } +}; + +// Exception registers + +static DNBRegisterInfo g_exc_registers[] = +{ + { "dar" , Uint, 4, Hex }, + { "dsisr" , Uint, 4, Hex }, + { "exception" , Uint, 4, Hex } +}; + +// Number of registers in each register set +const size_t k_num_gpr_registers = sizeof(g_gpr_registers)/sizeof(DNBRegisterInfo); +const size_t k_num_vfp_registers = sizeof(g_vfp_registers)/sizeof(DNBRegisterInfo); +const size_t k_num_exc_registers = sizeof(g_exc_registers)/sizeof(DNBRegisterInfo); +// Total number of registers for this architecture +const size_t k_num_armv6_registers = k_num_gpr_registers + k_num_vfp_registers + k_num_exc_registers; + +//---------------------------------------------------------------------- +// Register set definitions. The first definitions at register set index +// of zero is for all registers, followed by other registers sets. The +// register information for the all register set need not be filled in. +//---------------------------------------------------------------------- +static const DNBRegisterSetInfo g_reg_sets[] = +{ + { "ARMV6 Registers", NULL, k_num_armv6_registers }, + { "General Purpose Registers", g_gpr_registers, k_num_gpr_registers }, + { "Floating Point Registers", g_vfp_registers, k_num_vfp_registers }, + { "Exception State Registers", g_exc_registers, k_num_exc_registers } +}; +// Total number of register sets for this architecture +const size_t k_num_register_sets = sizeof(g_reg_sets)/sizeof(DNBRegisterSetInfo); + + +const DNBRegisterSetInfo * +DNBArchMachARM::GetRegisterSetInfo(nub_size_t *num_reg_sets) const +{ + *num_reg_sets = k_num_register_sets; + return g_reg_sets; +} + +bool +DNBArchMachARM::GetRegisterValue(int set, int reg, DNBRegisterValue *value) +{ + if (set == REGISTER_SET_GENERIC) + { + switch (reg) + { + case GENERIC_REGNUM_PC: // Program Counter + set = e_regSetGPR; + reg = e_regNumGPR_pc; + break; + + case GENERIC_REGNUM_SP: // Stack Pointer + set = e_regSetGPR; + reg = e_regNumGPR_sp; + break; + + case GENERIC_REGNUM_FP: // Frame Pointer + set = e_regSetGPR; + reg = e_regNumGPR_r7; // is this the right reg? + break; + + case GENERIC_REGNUM_RA: // Return Address + set = e_regSetGPR; + reg = e_regNumGPR_lr; + break; + + case GENERIC_REGNUM_FLAGS: // Processor flags register + set = e_regSetGPR; + reg = e_regNumGPR_cpsr; + break; + + default: + return false; + } + } + + if (GetRegisterState(set, false) != KERN_SUCCESS) + return false; + + const DNBRegisterInfo *regInfo = m_thread->GetRegisterInfo(set, reg); + if (regInfo) + { + value->info = *regInfo; + switch (set) + { + case e_regSetGPR: + if (reg < k_num_gpr_registers) + { + value->value.uint32 = m_state.gpr.__r[reg]; + return true; + } + break; + + case e_regSetVFP: + if (reg < 32) + { + value->value.uint32 = m_state.vfp.__r[reg]; + return true; + } + else if (reg == 32) + { + value->value.uint32 = m_state.vfp.__fpscr; + return true; + } + break; + + case e_regSetEXC: + if (reg < k_num_exc_registers) + { + value->value.uint32 = (&m_state.exc.__exception)[reg]; + return true; + } + break; + } + } + return false; +} + +bool +DNBArchMachARM::SetRegisterValue(int set, int reg, const DNBRegisterValue *value) +{ + if (set == REGISTER_SET_GENERIC) + { + switch (reg) + { + case GENERIC_REGNUM_PC: // Program Counter + set = e_regSetGPR; + reg = e_regNumGPR_pc; + break; + + case GENERIC_REGNUM_SP: // Stack Pointer + set = e_regSetGPR; + reg = e_regNumGPR_sp; + break; + + case GENERIC_REGNUM_FP: // Frame Pointer + set = e_regSetGPR; + reg = e_regNumGPR_r7; + break; + + case GENERIC_REGNUM_RA: // Return Address + set = e_regSetGPR; + reg = e_regNumGPR_lr; + break; + + case GENERIC_REGNUM_FLAGS: // Processor flags register + set = e_regSetGPR; + reg = e_regNumGPR_cpsr; + break; + + default: + return false; + } + } + + if (GetRegisterState(set, false) != KERN_SUCCESS) + return false; + + bool success = false; + const DNBRegisterInfo *regInfo = m_thread->GetRegisterInfo(set, reg); + if (regInfo) + { + switch (set) + { + case e_regSetGPR: + if (reg < k_num_gpr_registers) + { + m_state.gpr.__r[reg] = value->value.uint32; + success = true; + } + break; + + case e_regSetVFP: + if (reg < 32) + { + m_state.vfp.__r[reg] = value->value.float64; + success = true; + } + else if (reg == 32) + { + m_state.vfp.__fpscr = value->value.uint32; + success = true; + } + break; + + case e_regSetEXC: + if (reg < k_num_exc_registers) + { + (&m_state.exc.__exception)[reg] = value->value.uint32; + success = true; + } + break; + } + + } + if (success) + return SetRegisterState(set) == KERN_SUCCESS; + return false; +} + +kern_return_t +DNBArchMachARM::GetRegisterState(int set, bool force) +{ + switch (set) + { + case e_regSetALL: return GetGPRState(force) | + GetVFPState(force) | + GetEXCState(force) | + GetDBGState(force); + case e_regSetGPR: return GetGPRState(force); + case e_regSetVFP: return GetVFPState(force); + case e_regSetEXC: return GetEXCState(force); + case e_regSetDBG: return GetDBGState(force); + default: break; + } + return KERN_INVALID_ARGUMENT; +} + +kern_return_t +DNBArchMachARM::SetRegisterState(int set) +{ + // Make sure we have a valid context to set. + kern_return_t err = GetRegisterState(set, false); + if (err != KERN_SUCCESS) + return err; + + switch (set) + { + case e_regSetALL: return SetGPRState() | + SetVFPState() | + SetEXCState() | + SetDBGState(); + case e_regSetGPR: return SetGPRState(); + case e_regSetVFP: return SetVFPState(); + case e_regSetEXC: return SetEXCState(); + case e_regSetDBG: return SetDBGState(); + default: break; + } + return KERN_INVALID_ARGUMENT; +} + +bool +DNBArchMachARM::RegisterSetStateIsValid (int set) const +{ + return m_state.RegsAreValid(set); +} + + +#endif // #if defined (__arm__) + diff --git a/lldb/tools/debugserver/source/MacOSX/arm/DNBArchImpl.h b/lldb/tools/debugserver/source/MacOSX/arm/DNBArchImpl.h new file mode 100644 index 00000000000..f40c891ce98 --- /dev/null +++ b/lldb/tools/debugserver/source/MacOSX/arm/DNBArchImpl.h @@ -0,0 +1,217 @@ +//===-- DNBArchImpl.h -------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/25/07. +// +//===----------------------------------------------------------------------===// + +#ifndef __DebugNubArchMachARM_h__ +#define __DebugNubArchMachARM_h__ + +#if defined (__arm__) + +#include "DNBArch.h" +#include <ARMDisassembler/ARMDisassembler.h> + +class MachThread; + +class DNBArchMachARM : public DNBArchProtocol +{ +public: + enum { kMaxNumThumbITBreakpoints = 4 }; + + DNBArchMachARM(MachThread *thread) : + m_thread(thread), + m_state(), + m_hw_single_chained_step_addr(INVALID_NUB_ADDRESS), + m_sw_single_step_next_pc(INVALID_NUB_ADDRESS), + m_sw_single_step_break_id(INVALID_NUB_BREAK_ID), + m_sw_single_step_itblock_break_count(0), + m_last_decode_pc(INVALID_NUB_ADDRESS) + { + memset(&m_dbg_save, 0, sizeof(m_dbg_save)); + ThumbStaticsInit(&m_last_decode_thumb); + for (int i = 0; i < kMaxNumThumbITBreakpoints; i++) + m_sw_single_step_itblock_break_id[i] = INVALID_NUB_BREAK_ID; + } + + virtual ~DNBArchMachARM() + { + } + + virtual const DNBRegisterSetInfo * + GetRegisterSetInfo(nub_size_t *num_reg_sets) const; + virtual bool GetRegisterValue(int set, int reg, DNBRegisterValue *value); + virtual bool SetRegisterValue(int set, int reg, const DNBRegisterValue *value); + + virtual kern_return_t GetRegisterState (int set, bool force); + virtual kern_return_t SetRegisterState (int set); + virtual bool RegisterSetStateIsValid (int set) const; + + virtual uint64_t GetPC(uint64_t failValue); // Get program counter + virtual kern_return_t SetPC(uint64_t value); + virtual uint64_t GetSP(uint64_t failValue); // Get stack pointer + virtual void ThreadWillResume(); + virtual bool ThreadDidStop(); + + static const uint8_t * const SoftwareBreakpointOpcode (nub_size_t byte_size); + static uint32_t GetCPUType(); + + virtual uint32_t NumSupportedHardwareBreakpoints(); + virtual uint32_t NumSupportedHardwareWatchpoints(); + virtual uint32_t EnableHardwareBreakpoint (nub_addr_t addr, nub_size_t size); + virtual uint32_t EnableHardwareWatchpoint (nub_addr_t addr, nub_size_t size, bool read, bool write); + virtual bool DisableHardwareBreakpoint (uint32_t hw_break_index); + virtual bool DisableHardwareWatchpoint (uint32_t hw_break_index); + virtual bool StepNotComplete (); + +protected: + + + kern_return_t EnableHardwareSingleStep (bool enable); + kern_return_t SetSingleStepSoftwareBreakpoints (); + + bool ConditionPassed(uint8_t condition, uint32_t cpsr); + bool ComputeNextPC(nub_addr_t currentPC, arm_decoded_instruction_t decodedInstruction, bool currentPCIsThumb, nub_addr_t *targetPC); + void EvaluateNextInstructionForSoftwareBreakpointSetup(nub_addr_t currentPC, uint32_t cpsr, bool currentPCIsThumb, nub_addr_t *nextPC, bool *nextPCIsThumb); + void DecodeITBlockInstructions(nub_addr_t curr_pc); + arm_error_t DecodeInstructionUsingDisassembler(nub_addr_t curr_pc, uint32_t curr_cpsr, arm_decoded_instruction_t *decodedInstruction, thumb_static_data_t *thumbStaticData, nub_addr_t *next_pc); + static nub_bool_t BreakpointHit (nub_process_t pid, nub_thread_t tid, nub_break_t breakID, void *baton); + + typedef enum RegisterSetTag + { + e_regSetALL = REGISTER_SET_ALL, + e_regSetGPR = ARM_THREAD_STATE, + e_regSetVFP = ARM_VFP_STATE, + e_regSetEXC = ARM_EXCEPTION_STATE, + e_regSetDBG = ARM_DEBUG_STATE, + kNumRegisterSets + } RegisterSet; + + enum + { + Read = 0, + Write = 1, + kNumErrors = 2 + }; + + struct State + { + arm_thread_state_t gpr; + arm_vfp_state_t vfp; + arm_exception_state_t exc; + arm_debug_state_t dbg; + kern_return_t gpr_errs[2]; // Read/Write errors + kern_return_t vfp_errs[2]; // Read/Write errors + kern_return_t exc_errs[2]; // Read/Write errors + kern_return_t dbg_errs[2]; // Read/Write errors + State() + { + uint32_t i; + for (i=0; i<kNumErrors; i++) + { + gpr_errs[i] = -1; + vfp_errs[i] = -1; + exc_errs[i] = -1; + dbg_errs[i] = -1; + } + } + void InvalidateRegisterSetState(int set) + { + SetError (set, Read, -1); + } + kern_return_t GetError (int set, uint32_t err_idx) const + { + if (err_idx < kNumErrors) + { + switch (set) + { + // When getting all errors, just OR all values together to see if + // we got any kind of error. + case e_regSetALL: return gpr_errs[err_idx] | + vfp_errs[err_idx] | + exc_errs[err_idx] | + dbg_errs[err_idx] ; + case e_regSetGPR: return gpr_errs[err_idx]; + case e_regSetVFP: return vfp_errs[err_idx]; + case e_regSetEXC: return exc_errs[err_idx]; + case e_regSetDBG: return dbg_errs[err_idx]; + default: break; + } + } + return -1; + } + bool SetError (int set, uint32_t err_idx, kern_return_t err) + { + if (err_idx < kNumErrors) + { + switch (set) + { + case e_regSetALL: + gpr_errs[err_idx] = err; + vfp_errs[err_idx] = err; + dbg_errs[err_idx] = err; + exc_errs[err_idx] = err; + return true; + + case e_regSetGPR: + gpr_errs[err_idx] = err; + return true; + + case e_regSetVFP: + vfp_errs[err_idx] = err; + return true; + + case e_regSetEXC: + exc_errs[err_idx] = err; + return true; + + case e_regSetDBG: + dbg_errs[err_idx] = err; + return true; + default: break; + } + } + return false; + } + bool RegsAreValid (int set) const + { + return GetError(set, Read) == KERN_SUCCESS; + } + }; + + kern_return_t GetGPRState (bool force); + kern_return_t GetVFPState (bool force); + kern_return_t GetEXCState (bool force); + kern_return_t GetDBGState (bool force); + + kern_return_t SetGPRState (); + kern_return_t SetVFPState (); + kern_return_t SetEXCState (); + kern_return_t SetDBGState (); +protected: + MachThread * m_thread; + State m_state; + arm_debug_state_t m_dbg_save; + nub_addr_t m_hw_single_chained_step_addr; + // Software single stepping support + nub_addr_t m_sw_single_step_next_pc; + nub_break_t m_sw_single_step_break_id; + nub_break_t m_sw_single_step_itblock_break_id[kMaxNumThumbITBreakpoints]; + nub_addr_t m_sw_single_step_itblock_break_count; + // Disassembler state + thumb_static_data_t m_last_decode_thumb; + arm_decoded_instruction_t m_last_decode_arm; + nub_addr_t m_last_decode_pc; + +}; + +typedef DNBArchMachARM DNBArch; +#endif // #if defined (__arm__) +#endif // #ifndef __DebugNubArchMachARM_h__ diff --git a/lldb/tools/debugserver/source/MacOSX/dbgnub-mig.defs b/lldb/tools/debugserver/source/MacOSX/dbgnub-mig.defs new file mode 100644 index 00000000000..9b1554f8708 --- /dev/null +++ b/lldb/tools/debugserver/source/MacOSX/dbgnub-mig.defs @@ -0,0 +1,16 @@ +/* + * nub.defs + */ + +/* + * DNBConfig.h is autogenerated by a perl script that is run as a build + * script in XCode. XCode is responsible for calling the script and setting + * the include paths correctly to locate it. The file will exist in the + * derived sources directory in the build folder. + * + */ + +#include "DNBConfig.h" + + +#import <mach/mach_exc.defs> diff --git a/lldb/tools/debugserver/source/MacOSX/i386/DNBArchImplI386.cpp b/lldb/tools/debugserver/source/MacOSX/i386/DNBArchImplI386.cpp new file mode 100644 index 00000000000..0a0601f6378 --- /dev/null +++ b/lldb/tools/debugserver/source/MacOSX/i386/DNBArchImplI386.cpp @@ -0,0 +1,879 @@ +//===-- DNBArchImplI386.cpp -------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/25/07. +// +//===----------------------------------------------------------------------===// + +#if defined (__i386__) + +#include <sys/cdefs.h> + +#include "MacOSX/i386/DNBArchImplI386.h" +#include "DNBLog.h" +#include "MachThread.h" +#include "MachProcess.h" + +static const uint8_t g_breakpoint_opcode[] = { 0xCC }; + +enum +{ + gpr_eax = 0, + gpr_ebx = 1, + gpr_ecx = 2, + gpr_edx = 3, + gpr_edi = 4, + gpr_esi = 5, + gpr_ebp = 6, + gpr_esp = 7, + gpr_ss = 8, + gpr_eflags = 9, + gpr_eip = 10, + gpr_cs = 11, + gpr_ds = 12, + gpr_es = 13, + gpr_fs = 14, + gpr_gs = 15, + k_num_gpr_regs +}; + +enum { + fpu_fcw, + fpu_fsw, + fpu_ftw, + fpu_fop, + fpu_ip, + fpu_cs, + fpu_dp, + fpu_ds, + fpu_mxcsr, + fpu_mxcsrmask, + fpu_stmm0, + fpu_stmm1, + fpu_stmm2, + fpu_stmm3, + fpu_stmm4, + fpu_stmm5, + fpu_stmm6, + fpu_stmm7, + fpu_xmm0, + fpu_xmm1, + fpu_xmm2, + fpu_xmm3, + fpu_xmm4, + fpu_xmm5, + fpu_xmm6, + fpu_xmm7, + k_num_fpu_regs, + + // Aliases + fpu_fctrl = fpu_fcw, + fpu_fstat = fpu_fsw, + fpu_ftag = fpu_ftw, + fpu_fiseg = fpu_cs, + fpu_fioff = fpu_ip, + fpu_foseg = fpu_ds, + fpu_fooff = fpu_dp +}; + +enum { + exc_trapno, + exc_err, + exc_faultvaddr, + k_num_exc_regs, +}; + + +enum +{ + gcc_eax = 0, + gcc_ecx, + gcc_edx, + gcc_ebx, + gcc_ebp, + gcc_esp, + gcc_esi, + gcc_edi, + gcc_eip, + gcc_eflags +}; + +enum +{ + dwarf_eax = 0, + dwarf_ecx, + dwarf_edx, + dwarf_ebx, + dwarf_esp, + dwarf_ebp, + dwarf_esi, + dwarf_edi, + dwarf_eip, + dwarf_eflags, + dwarf_stmm0 = 11, + dwarf_stmm1, + dwarf_stmm2, + dwarf_stmm3, + dwarf_stmm4, + dwarf_stmm5, + dwarf_stmm6, + dwarf_stmm7, + dwarf_xmm0 = 21, + dwarf_xmm1, + dwarf_xmm2, + dwarf_xmm3, + dwarf_xmm4, + dwarf_xmm5, + dwarf_xmm6, + dwarf_xmm7 +}; + +enum +{ + gdb_eax = 0, + gdb_ecx = 1, + gdb_edx = 2, + gdb_ebx = 3, + gdb_esp = 4, + gdb_ebp = 5, + gdb_esi = 6, + gdb_edi = 7, + gdb_eip = 8, + gdb_eflags = 9, + gdb_cs = 10, + gdb_ss = 11, + gdb_ds = 12, + gdb_es = 13, + gdb_fs = 14, + gdb_gs = 15, + gdb_stmm0 = 16, + gdb_stmm1 = 17, + gdb_stmm2 = 18, + gdb_stmm3 = 19, + gdb_stmm4 = 20, + gdb_stmm5 = 21, + gdb_stmm6 = 22, + gdb_stmm7 = 23, + gdb_fctrl = 24, gdb_fcw = gdb_fctrl, + gdb_fstat = 25, gdb_fsw = gdb_fstat, + gdb_ftag = 26, gdb_ftw = gdb_ftag, + gdb_fiseg = 27, gdb_fpu_cs = gdb_fiseg, + gdb_fioff = 28, gdb_ip = gdb_fioff, + gdb_foseg = 29, gdb_fpu_ds = gdb_foseg, + gdb_fooff = 30, gdb_dp = gdb_fooff, + gdb_fop = 31, + gdb_xmm0 = 32, + gdb_xmm1 = 33, + gdb_xmm2 = 34, + gdb_xmm3 = 35, + gdb_xmm4 = 36, + gdb_xmm5 = 37, + gdb_xmm6 = 38, + gdb_xmm7 = 39, + gdb_mxcsr = 40, + gdb_mm0 = 41, + gdb_mm1 = 42, + gdb_mm2 = 43, + gdb_mm3 = 44, + gdb_mm4 = 45, + gdb_mm5 = 46, + gdb_mm6 = 47, + gdb_mm7 = 48 +}; + + +const uint8_t * const +DNBArchImplI386::SoftwareBreakpointOpcode (nub_size_t byte_size) +{ + if (byte_size == 1) + return g_breakpoint_opcode; + return NULL; +} + +uint32_t +DNBArchImplI386::GetCPUType() +{ + return CPU_TYPE_I386; +} + +uint64_t +DNBArchImplI386::GetPC(uint64_t failValue) +{ + // Get program counter + if (GetGPRState(false) == KERN_SUCCESS) + return m_state.context.gpr.__eip; + return failValue; +} + +kern_return_t +DNBArchImplI386::SetPC(uint64_t value) +{ + // Get program counter + kern_return_t err = GetGPRState(false); + if (err == KERN_SUCCESS) + { + m_state.context.gpr.__eip = value; + err = SetGPRState(); + } + return err == KERN_SUCCESS; +} + +uint64_t +DNBArchImplI386::GetSP(uint64_t failValue) +{ + // Get stack pointer + if (GetGPRState(false) == KERN_SUCCESS) + return m_state.context.gpr.__esp; + return failValue; +} + +// Uncomment the value below to verify the values in the debugger. +//#define DEBUG_GPR_VALUES 1 // DO NOT CHECK IN WITH THIS DEFINE ENABLED +//#define SET_GPR(reg) m_state.context.gpr.__##reg = gpr_##reg + +kern_return_t +DNBArchImplI386::GetGPRState(bool force) +{ + if (force || m_state.GetError(e_regSetGPR, Read)) + { +#if DEBUG_GPR_VALUES + SET_GPR(eax); + SET_GPR(ebx); + SET_GPR(ecx); + SET_GPR(edx); + SET_GPR(edi); + SET_GPR(esi); + SET_GPR(ebp); + SET_GPR(esp); + SET_GPR(ss); + SET_GPR(eflags); + SET_GPR(eip); + SET_GPR(cs); + SET_GPR(ds); + SET_GPR(es); + SET_GPR(fs); + SET_GPR(gs); + m_state.SetError(e_regSetGPR, Read, 0); +#else + mach_msg_type_number_t count = e_regSetWordSizeGPR; + m_state.SetError(e_regSetGPR, Read, ::thread_get_state(m_thread->ThreadID(), x86_THREAD_STATE32, (thread_state_t)&m_state.context.gpr, &count)); +#endif + } + return m_state.GetError(e_regSetGPR, Read); +} + +// Uncomment the value below to verify the values in the debugger. +//#define DEBUG_FPU_VALUES 1 // DO NOT CHECK IN WITH THIS DEFINE ENABLED + +kern_return_t +DNBArchImplI386::GetFPUState(bool force) +{ + if (force || m_state.GetError(e_regSetFPU, Read)) + { +#if DEBUG_FPU_VALUES + m_state.context.fpu.__fpu_reserved[0] = -1; + m_state.context.fpu.__fpu_reserved[1] = -1; + *(uint16_t *)&(m_state.context.fpu.__fpu_fcw) = 0x1234; + *(uint16_t *)&(m_state.context.fpu.__fpu_fsw) = 0x5678; + m_state.context.fpu.__fpu_ftw = 1; + m_state.context.fpu.__fpu_rsrv1 = UINT8_MAX; + m_state.context.fpu.__fpu_fop = 2; + m_state.context.fpu.__fpu_ip = 3; + m_state.context.fpu.__fpu_cs = 4; + m_state.context.fpu.__fpu_rsrv2 = 5; + m_state.context.fpu.__fpu_dp = 6; + m_state.context.fpu.__fpu_ds = 7; + m_state.context.fpu.__fpu_rsrv3 = UINT16_MAX; + m_state.context.fpu.__fpu_mxcsr = 8; + m_state.context.fpu.__fpu_mxcsrmask = 9; + int i; + for (i=0; i<16; ++i) + { + if (i<10) + { + m_state.context.fpu.__fpu_stmm0.__mmst_reg[i] = 'a'; + m_state.context.fpu.__fpu_stmm1.__mmst_reg[i] = 'b'; + m_state.context.fpu.__fpu_stmm2.__mmst_reg[i] = 'c'; + m_state.context.fpu.__fpu_stmm3.__mmst_reg[i] = 'd'; + m_state.context.fpu.__fpu_stmm4.__mmst_reg[i] = 'e'; + m_state.context.fpu.__fpu_stmm5.__mmst_reg[i] = 'f'; + m_state.context.fpu.__fpu_stmm6.__mmst_reg[i] = 'g'; + m_state.context.fpu.__fpu_stmm7.__mmst_reg[i] = 'h'; + } + else + { + m_state.context.fpu.__fpu_stmm0.__mmst_reg[i] = INT8_MIN; + m_state.context.fpu.__fpu_stmm1.__mmst_reg[i] = INT8_MIN; + m_state.context.fpu.__fpu_stmm2.__mmst_reg[i] = INT8_MIN; + m_state.context.fpu.__fpu_stmm3.__mmst_reg[i] = INT8_MIN; + m_state.context.fpu.__fpu_stmm4.__mmst_reg[i] = INT8_MIN; + m_state.context.fpu.__fpu_stmm5.__mmst_reg[i] = INT8_MIN; + m_state.context.fpu.__fpu_stmm6.__mmst_reg[i] = INT8_MIN; + m_state.context.fpu.__fpu_stmm7.__mmst_reg[i] = INT8_MIN; + } + + m_state.context.fpu.__fpu_xmm0.__xmm_reg[i] = '0'; + m_state.context.fpu.__fpu_xmm1.__xmm_reg[i] = '1'; + m_state.context.fpu.__fpu_xmm2.__xmm_reg[i] = '2'; + m_state.context.fpu.__fpu_xmm3.__xmm_reg[i] = '3'; + m_state.context.fpu.__fpu_xmm4.__xmm_reg[i] = '4'; + m_state.context.fpu.__fpu_xmm5.__xmm_reg[i] = '5'; + m_state.context.fpu.__fpu_xmm6.__xmm_reg[i] = '6'; + m_state.context.fpu.__fpu_xmm7.__xmm_reg[i] = '7'; + } + for (i=0; i<sizeof(m_state.context.fpu.__fpu_rsrv4); ++i) + m_state.context.fpu.__fpu_rsrv4[i] = INT8_MIN; + m_state.context.fpu.__fpu_reserved1 = -1; + m_state.SetError(e_regSetFPU, Read, 0); +#else + mach_msg_type_number_t count = e_regSetWordSizeFPR; + m_state.SetError(e_regSetFPU, Read, ::thread_get_state(m_thread->ThreadID(), x86_FLOAT_STATE32, (thread_state_t)&m_state.context.fpu, &count)); +#endif + } + return m_state.GetError(e_regSetFPU, Read); +} + +kern_return_t +DNBArchImplI386::GetEXCState(bool force) +{ + if (force || m_state.GetError(e_regSetEXC, Read)) + { + mach_msg_type_number_t count = e_regSetWordSizeEXC; + m_state.SetError(e_regSetEXC, Read, ::thread_get_state(m_thread->ThreadID(), x86_EXCEPTION_STATE32, (thread_state_t)&m_state.context.exc, &count)); + } + return m_state.GetError(e_regSetEXC, Read); +} + +kern_return_t +DNBArchImplI386::SetGPRState() +{ + m_state.SetError(e_regSetGPR, Write, ::thread_set_state(m_thread->ThreadID(), x86_THREAD_STATE32, (thread_state_t)&m_state.context.gpr, e_regSetWordSizeGPR)); + return m_state.GetError(e_regSetGPR, Write); +} + +kern_return_t +DNBArchImplI386::SetFPUState() +{ + m_state.SetError(e_regSetFPU, Write, ::thread_set_state(m_thread->ThreadID(), x86_FLOAT_STATE32, (thread_state_t)&m_state.context.fpu, e_regSetWordSizeFPR)); + return m_state.GetError(e_regSetFPU, Write); +} + +kern_return_t +DNBArchImplI386::SetEXCState() +{ + m_state.SetError(e_regSetEXC, Write, ::thread_set_state(m_thread->ThreadID(), x86_EXCEPTION_STATE32, (thread_state_t)&m_state.context.exc, e_regSetWordSizeEXC)); + return m_state.GetError(e_regSetEXC, Write); +} + +void +DNBArchImplI386::ThreadWillResume() +{ + // Do we need to step this thread? If so, let the mach thread tell us so. + if (m_thread->IsStepping()) + { + // This is the primary thread, let the arch do anything it needs + EnableHardwareSingleStep(true) == KERN_SUCCESS; + } +} + +bool +DNBArchImplI386::ThreadDidStop() +{ + bool success = true; + + m_state.InvalidateAllRegisterStates(); + + // Are we stepping a single instruction? + if (GetGPRState(true) == KERN_SUCCESS) + { + // We are single stepping, was this the primary thread? + if (m_thread->IsStepping()) + { + // This was the primary thread, we need to clear the trace + // bit if so. + success = EnableHardwareSingleStep(false) == KERN_SUCCESS; + } + else + { + // The MachThread will automatically restore the suspend count + // in ThreadDidStop(), so we don't need to do anything here if + // we weren't the primary thread the last time + } + } + return success; +} + +bool +DNBArchImplI386::NotifyException(MachException::Data& exc) +{ + switch (exc.exc_type) + { + case EXC_BAD_ACCESS: + break; + case EXC_BAD_INSTRUCTION: + break; + case EXC_ARITHMETIC: + break; + case EXC_EMULATION: + break; + case EXC_SOFTWARE: + break; + case EXC_BREAKPOINT: + if (exc.exc_data.size() >= 2 && exc.exc_data[0] == 2) + { + nub_addr_t pc = GetPC(INVALID_NUB_ADDRESS); + if (pc != INVALID_NUB_ADDRESS && pc > 0) + { + pc -= 1; + // Check for a breakpoint at one byte prior to the current PC value + // since the PC will be just past the trap. + + nub_break_t breakID = m_thread->Process()->Breakpoints().FindIDByAddress(pc); + if (NUB_BREAK_ID_IS_VALID(breakID)) + { + // Backup the PC for i386 since the trap was taken and the PC + // is at the address following the single byte trap instruction. + if (m_state.context.gpr.__eip > 0) + { + m_state.context.gpr.__eip = pc; + // Write the new PC back out + SetGPRState (); + } + + m_thread->SetCurrentBreakpoint(breakID); + } + return true; + } + } + break; + case EXC_SYSCALL: + break; + case EXC_MACH_SYSCALL: + break; + case EXC_RPC_ALERT: + break; + } + return false; +} + + +// Set the single step bit in the processor status register. +kern_return_t +DNBArchImplI386::EnableHardwareSingleStep (bool enable) +{ + if (GetGPRState(false) == KERN_SUCCESS) + { + const uint32_t trace_bit = 0x100u; + if (enable) + m_state.context.gpr.__eflags |= trace_bit; + else + m_state.context.gpr.__eflags &= ~trace_bit; + return SetGPRState(); + } + return m_state.GetError(e_regSetGPR, Read); +} + + +//---------------------------------------------------------------------- +// Register information defintions +//---------------------------------------------------------------------- + + +#define GPR_OFFSET(reg) (offsetof (DNBArchImplI386::GPR, __##reg)) +#define FPU_OFFSET(reg) (offsetof (DNBArchImplI386::FPU, __fpu_##reg) + offsetof (DNBArchImplI386::Context, fpu)) +#define EXC_OFFSET(reg) (offsetof (DNBArchImplI386::EXC, __##reg) + offsetof (DNBArchImplI386::Context, exc)) + +#define GPR_SIZE(reg) (sizeof(((DNBArchImplI386::GPR *)NULL)->__##reg)) +#define FPU_SIZE_UINT(reg) (sizeof(((DNBArchImplI386::FPU *)NULL)->__fpu_##reg)) +#define FPU_SIZE_MMST(reg) (sizeof(((DNBArchImplI386::FPU *)NULL)->__fpu_##reg.__mmst_reg)) +#define FPU_SIZE_XMM(reg) (sizeof(((DNBArchImplI386::FPU *)NULL)->__fpu_##reg.__xmm_reg)) +#define EXC_SIZE(reg) (sizeof(((DNBArchImplI386::EXC *)NULL)->__##reg)) + +// These macros will auto define the register name, alt name, register size, +// register offset, encoding, format and native register. This ensures that +// the register state structures are defined correctly and have the correct +// sizes and offsets. + +// General purpose registers for 64 bit +const DNBRegisterInfo +DNBArchImplI386::g_gpr_registers[] = +{ +{ e_regSetGPR, gpr_eax, "eax" , NULL , Uint, Hex, GPR_SIZE(eax), GPR_OFFSET(eax) , gcc_eax , dwarf_eax , -1 , gdb_eax }, +{ e_regSetGPR, gpr_ebx, "ebx" , NULL , Uint, Hex, GPR_SIZE(ebx), GPR_OFFSET(ebx) , gcc_ebx , dwarf_ebx , -1 , gdb_ebx }, +{ e_regSetGPR, gpr_ecx, "ecx" , NULL , Uint, Hex, GPR_SIZE(ecx), GPR_OFFSET(ecx) , gcc_ecx , dwarf_ecx , -1 , gdb_ecx }, +{ e_regSetGPR, gpr_edx, "edx" , NULL , Uint, Hex, GPR_SIZE(edx), GPR_OFFSET(edx) , gcc_edx , dwarf_edx , -1 , gdb_edx }, +{ e_regSetGPR, gpr_edi, "edi" , NULL , Uint, Hex, GPR_SIZE(edi), GPR_OFFSET(edi) , gcc_edi , dwarf_edi , -1 , gdb_edi }, +{ e_regSetGPR, gpr_esi, "esi" , NULL , Uint, Hex, GPR_SIZE(esi), GPR_OFFSET(esi) , gcc_esi , dwarf_esi , -1 , gdb_esi }, +{ e_regSetGPR, gpr_ebp, "ebp" , "fp" , Uint, Hex, GPR_SIZE(ebp), GPR_OFFSET(ebp) , gcc_ebp , dwarf_ebp , GENERIC_REGNUM_FP , gdb_ebp }, +{ e_regSetGPR, gpr_esp, "esp" , "sp" , Uint, Hex, GPR_SIZE(esp), GPR_OFFSET(esp) , gcc_esp , dwarf_esp , GENERIC_REGNUM_SP , gdb_esp }, +{ e_regSetGPR, gpr_ss, "ss" , NULL , Uint, Hex, GPR_SIZE(ss), GPR_OFFSET(ss) , -1 , -1 , -1 , gdb_ss }, +{ e_regSetGPR, gpr_eflags, "eflags", "flags" , Uint, Hex, GPR_SIZE(eflags), GPR_OFFSET(eflags) , gcc_eflags, dwarf_eflags , GENERIC_REGNUM_FLAGS , gdb_eflags}, +{ e_regSetGPR, gpr_eip, "eip" , "pc" , Uint, Hex, GPR_SIZE(eip), GPR_OFFSET(eip) , gcc_eip , dwarf_eip , GENERIC_REGNUM_PC , gdb_eip }, +{ e_regSetGPR, gpr_cs, "cs" , NULL , Uint, Hex, GPR_SIZE(cs), GPR_OFFSET(cs) , -1 , -1 , -1 , gdb_cs }, +{ e_regSetGPR, gpr_ds, "ds" , NULL , Uint, Hex, GPR_SIZE(ds), GPR_OFFSET(ds) , -1 , -1 , -1 , gdb_ds }, +{ e_regSetGPR, gpr_es, "es" , NULL , Uint, Hex, GPR_SIZE(es), GPR_OFFSET(es) , -1 , -1 , -1 , gdb_es }, +{ e_regSetGPR, gpr_fs, "fs" , NULL , Uint, Hex, GPR_SIZE(fs), GPR_OFFSET(fs) , -1 , -1 , -1 , gdb_fs }, +{ e_regSetGPR, gpr_gs, "gs" , NULL , Uint, Hex, GPR_SIZE(gs), GPR_OFFSET(gs) , -1 , -1 , -1 , gdb_gs } +}; + + +const DNBRegisterInfo +DNBArchImplI386::g_fpu_registers[] = +{ +{ e_regSetFPU, fpu_fcw , "fctrl" , NULL, Uint, Hex, FPU_SIZE_UINT(fcw) , FPU_OFFSET(fcw) , -1, -1, -1, -1 }, +{ e_regSetFPU, fpu_fsw , "fstat" , NULL, Uint, Hex, FPU_SIZE_UINT(fsw) , FPU_OFFSET(fsw) , -1, -1, -1, -1 }, +{ e_regSetFPU, fpu_ftw , "ftag" , NULL, Uint, Hex, FPU_SIZE_UINT(ftw) , FPU_OFFSET(ftw) , -1, -1, -1, -1 }, +{ e_regSetFPU, fpu_fop , "fop" , NULL, Uint, Hex, FPU_SIZE_UINT(fop) , FPU_OFFSET(fop) , -1, -1, -1, -1 }, +{ e_regSetFPU, fpu_ip , "fioff" , NULL, Uint, Hex, FPU_SIZE_UINT(ip) , FPU_OFFSET(ip) , -1, -1, -1, -1 }, +{ e_regSetFPU, fpu_cs , "fiseg" , NULL, Uint, Hex, FPU_SIZE_UINT(cs) , FPU_OFFSET(cs) , -1, -1, -1, -1 }, +{ e_regSetFPU, fpu_dp , "fooff" , NULL, Uint, Hex, FPU_SIZE_UINT(dp) , FPU_OFFSET(dp) , -1, -1, -1, -1 }, +{ e_regSetFPU, fpu_ds , "foseg" , NULL, Uint, Hex, FPU_SIZE_UINT(ds) , FPU_OFFSET(ds) , -1, -1, -1, -1 }, +{ e_regSetFPU, fpu_mxcsr , "mxcsr" , NULL, Uint, Hex, FPU_SIZE_UINT(mxcsr) , FPU_OFFSET(mxcsr) , -1, -1, -1, -1 }, +{ e_regSetFPU, fpu_mxcsrmask, "mxcsrmask" , NULL, Uint, Hex, FPU_SIZE_UINT(mxcsrmask) , FPU_OFFSET(mxcsrmask) , -1, -1, -1, -1 }, + +{ e_regSetFPU, fpu_stmm0, "stmm0", NULL, Vector, VectorOfUInt8, FPU_SIZE_MMST(stmm0), FPU_OFFSET(stmm0), -1, dwarf_stmm0, -1, gdb_stmm0 }, +{ e_regSetFPU, fpu_stmm1, "stmm1", NULL, Vector, VectorOfUInt8, FPU_SIZE_MMST(stmm1), FPU_OFFSET(stmm1), -1, dwarf_stmm1, -1, gdb_stmm1 }, +{ e_regSetFPU, fpu_stmm2, "stmm2", NULL, Vector, VectorOfUInt8, FPU_SIZE_MMST(stmm2), FPU_OFFSET(stmm2), -1, dwarf_stmm2, -1, gdb_stmm2 }, +{ e_regSetFPU, fpu_stmm3, "stmm3", NULL, Vector, VectorOfUInt8, FPU_SIZE_MMST(stmm3), FPU_OFFSET(stmm3), -1, dwarf_stmm3, -1, gdb_stmm3 }, +{ e_regSetFPU, fpu_stmm4, "stmm4", NULL, Vector, VectorOfUInt8, FPU_SIZE_MMST(stmm4), FPU_OFFSET(stmm4), -1, dwarf_stmm4, -1, gdb_stmm4 }, +{ e_regSetFPU, fpu_stmm5, "stmm5", NULL, Vector, VectorOfUInt8, FPU_SIZE_MMST(stmm5), FPU_OFFSET(stmm5), -1, dwarf_stmm5, -1, gdb_stmm5 }, +{ e_regSetFPU, fpu_stmm6, "stmm6", NULL, Vector, VectorOfUInt8, FPU_SIZE_MMST(stmm6), FPU_OFFSET(stmm6), -1, dwarf_stmm6, -1, gdb_stmm6 }, +{ e_regSetFPU, fpu_stmm7, "stmm7", NULL, Vector, VectorOfUInt8, FPU_SIZE_MMST(stmm7), FPU_OFFSET(stmm7), -1, dwarf_stmm7, -1, gdb_stmm7 }, + +{ e_regSetFPU, fpu_xmm0, "xmm0", NULL, Vector, VectorOfUInt8, FPU_SIZE_XMM(xmm0), FPU_OFFSET(xmm0), -1, dwarf_xmm0, -1, gdb_xmm0 }, +{ e_regSetFPU, fpu_xmm1, "xmm1", NULL, Vector, VectorOfUInt8, FPU_SIZE_XMM(xmm1), FPU_OFFSET(xmm1), -1, dwarf_xmm1, -1, gdb_xmm1 }, +{ e_regSetFPU, fpu_xmm2, "xmm2", NULL, Vector, VectorOfUInt8, FPU_SIZE_XMM(xmm2), FPU_OFFSET(xmm2), -1, dwarf_xmm2, -1, gdb_xmm2 }, +{ e_regSetFPU, fpu_xmm3, "xmm3", NULL, Vector, VectorOfUInt8, FPU_SIZE_XMM(xmm3), FPU_OFFSET(xmm3), -1, dwarf_xmm3, -1, gdb_xmm3 }, +{ e_regSetFPU, fpu_xmm4, "xmm4", NULL, Vector, VectorOfUInt8, FPU_SIZE_XMM(xmm4), FPU_OFFSET(xmm4), -1, dwarf_xmm4, -1, gdb_xmm4 }, +{ e_regSetFPU, fpu_xmm5, "xmm5", NULL, Vector, VectorOfUInt8, FPU_SIZE_XMM(xmm5), FPU_OFFSET(xmm5), -1, dwarf_xmm5, -1, gdb_xmm5 }, +{ e_regSetFPU, fpu_xmm6, "xmm6", NULL, Vector, VectorOfUInt8, FPU_SIZE_XMM(xmm6), FPU_OFFSET(xmm6), -1, dwarf_xmm6, -1, gdb_xmm6 }, +{ e_regSetFPU, fpu_xmm7, "xmm7", NULL, Vector, VectorOfUInt8, FPU_SIZE_XMM(xmm7), FPU_OFFSET(xmm7), -1, dwarf_xmm7, -1, gdb_xmm7 } +}; + + + +const DNBRegisterInfo +DNBArchImplI386::g_exc_registers[] = +{ +{ e_regSetEXC, exc_trapno, "trapno" , NULL, Uint, Hex, EXC_SIZE (trapno) , EXC_OFFSET (trapno) , -1, -1, -1, -1 }, +{ e_regSetEXC, exc_err, "err" , NULL, Uint, Hex, EXC_SIZE (err) , EXC_OFFSET (err) , -1, -1, -1, -1 }, +{ e_regSetEXC, exc_faultvaddr, "faultvaddr", NULL, Uint, Hex, EXC_SIZE (faultvaddr), EXC_OFFSET (faultvaddr) , -1, -1, -1, -1 } +}; + +// Number of registers in each register set +const size_t DNBArchImplI386::k_num_gpr_registers = sizeof(g_gpr_registers)/sizeof(DNBRegisterInfo); +const size_t DNBArchImplI386::k_num_fpu_registers = sizeof(g_fpu_registers)/sizeof(DNBRegisterInfo); +const size_t DNBArchImplI386::k_num_exc_registers = sizeof(g_exc_registers)/sizeof(DNBRegisterInfo); +const size_t DNBArchImplI386::k_num_all_registers = k_num_gpr_registers + k_num_fpu_registers + k_num_exc_registers; + +//---------------------------------------------------------------------- +// Register set definitions. The first definitions at register set index +// of zero is for all registers, followed by other registers sets. The +// register information for the all register set need not be filled in. +//---------------------------------------------------------------------- +const DNBRegisterSetInfo +DNBArchImplI386::g_reg_sets[] = +{ + { "i386 Registers", NULL, k_num_all_registers }, + { "General Purpose Registers", g_gpr_registers, k_num_gpr_registers }, + { "Floating Point Registers", g_fpu_registers, k_num_fpu_registers }, + { "Exception State Registers", g_exc_registers, k_num_exc_registers } +}; +// Total number of register sets for this architecture +const size_t DNBArchImplI386::k_num_register_sets = sizeof(g_reg_sets)/sizeof(DNBRegisterSetInfo); + + +const DNBRegisterSetInfo * +DNBArchImplI386::GetRegisterSetInfo(nub_size_t *num_reg_sets) +{ + *num_reg_sets = k_num_register_sets; + return g_reg_sets; +} + +bool +DNBArchImplI386::GetRegisterValue(int set, int reg, DNBRegisterValue *value) +{ + if (set == REGISTER_SET_GENERIC) + { + switch (reg) + { + case GENERIC_REGNUM_PC: // Program Counter + set = e_regSetGPR; + reg = gpr_eip; + break; + + case GENERIC_REGNUM_SP: // Stack Pointer + set = e_regSetGPR; + reg = gpr_esp; + break; + + case GENERIC_REGNUM_FP: // Frame Pointer + set = e_regSetGPR; + reg = gpr_ebp; + break; + + case GENERIC_REGNUM_FLAGS: // Processor flags register + set = e_regSetGPR; + reg = gpr_eflags; + break; + + case GENERIC_REGNUM_RA: // Return Address + default: + return false; + } + } + + if (GetRegisterState(set, false) != KERN_SUCCESS) + return false; + + const DNBRegisterInfo *regInfo = m_thread->GetRegisterInfo(set, reg); + if (regInfo) + { + value->info = *regInfo; + switch (set) + { + case e_regSetGPR: + if (reg < k_num_gpr_registers) + { + value->value.uint32 = ((uint32_t*)(&m_state.context.gpr))[reg]; + return true; + } + break; + + case e_regSetFPU: + switch (reg) + { + case fpu_fcw: value->value.uint16 = *((uint16_t *)(&m_state.context.fpu.__fpu_fcw)); return true; + case fpu_fsw: value->value.uint16 = *((uint16_t *)(&m_state.context.fpu.__fpu_fsw)); return true; + case fpu_ftw: value->value.uint8 = m_state.context.fpu.__fpu_ftw; return true; + case fpu_fop: value->value.uint16 = m_state.context.fpu.__fpu_fop; return true; + case fpu_ip: value->value.uint32 = m_state.context.fpu.__fpu_ip; return true; + case fpu_cs: value->value.uint16 = m_state.context.fpu.__fpu_cs; return true; + case fpu_dp: value->value.uint32 = m_state.context.fpu.__fpu_dp; return true; + case fpu_ds: value->value.uint16 = m_state.context.fpu.__fpu_ds; return true; + case fpu_mxcsr: value->value.uint32 = m_state.context.fpu.__fpu_mxcsr; return true; + case fpu_mxcsrmask: value->value.uint32 = m_state.context.fpu.__fpu_mxcsrmask; return true; + + case fpu_stmm0: memcpy(&value->value.uint8, m_state.context.fpu.__fpu_stmm0.__mmst_reg, 10); return true; + case fpu_stmm1: memcpy(&value->value.uint8, m_state.context.fpu.__fpu_stmm1.__mmst_reg, 10); return true; + case fpu_stmm2: memcpy(&value->value.uint8, m_state.context.fpu.__fpu_stmm2.__mmst_reg, 10); return true; + case fpu_stmm3: memcpy(&value->value.uint8, m_state.context.fpu.__fpu_stmm3.__mmst_reg, 10); return true; + case fpu_stmm4: memcpy(&value->value.uint8, m_state.context.fpu.__fpu_stmm4.__mmst_reg, 10); return true; + case fpu_stmm5: memcpy(&value->value.uint8, m_state.context.fpu.__fpu_stmm5.__mmst_reg, 10); return true; + case fpu_stmm6: memcpy(&value->value.uint8, m_state.context.fpu.__fpu_stmm6.__mmst_reg, 10); return true; + case fpu_stmm7: memcpy(&value->value.uint8, m_state.context.fpu.__fpu_stmm7.__mmst_reg, 10); return true; + + case fpu_xmm0: memcpy(&value->value.uint8, m_state.context.fpu.__fpu_xmm0.__xmm_reg, 16); return true; + case fpu_xmm1: memcpy(&value->value.uint8, m_state.context.fpu.__fpu_xmm1.__xmm_reg, 16); return true; + case fpu_xmm2: memcpy(&value->value.uint8, m_state.context.fpu.__fpu_xmm2.__xmm_reg, 16); return true; + case fpu_xmm3: memcpy(&value->value.uint8, m_state.context.fpu.__fpu_xmm3.__xmm_reg, 16); return true; + case fpu_xmm4: memcpy(&value->value.uint8, m_state.context.fpu.__fpu_xmm4.__xmm_reg, 16); return true; + case fpu_xmm5: memcpy(&value->value.uint8, m_state.context.fpu.__fpu_xmm5.__xmm_reg, 16); return true; + case fpu_xmm6: memcpy(&value->value.uint8, m_state.context.fpu.__fpu_xmm6.__xmm_reg, 16); return true; + case fpu_xmm7: memcpy(&value->value.uint8, m_state.context.fpu.__fpu_xmm7.__xmm_reg, 16); return true; + } + break; + + case e_regSetEXC: + if (reg < k_num_exc_registers) + { + value->value.uint32 = (&m_state.context.exc.__trapno)[reg]; + return true; + } + break; + } + } + return false; +} + + +bool +DNBArchImplI386::SetRegisterValue(int set, int reg, const DNBRegisterValue *value) +{ + if (set == REGISTER_SET_GENERIC) + { + switch (reg) + { + case GENERIC_REGNUM_PC: // Program Counter + set = e_regSetGPR; + reg = gpr_eip; + break; + + case GENERIC_REGNUM_SP: // Stack Pointer + set = e_regSetGPR; + reg = gpr_esp; + break; + + case GENERIC_REGNUM_FP: // Frame Pointer + set = e_regSetGPR; + reg = gpr_ebp; + break; + + case GENERIC_REGNUM_FLAGS: // Processor flags register + set = e_regSetGPR; + reg = gpr_eflags; + break; + + case GENERIC_REGNUM_RA: // Return Address + default: + return false; + } + } + + if (GetRegisterState(set, false) != KERN_SUCCESS) + return false; + + bool success = false; + const DNBRegisterInfo *regInfo = m_thread->GetRegisterInfo(set, reg); + if (regInfo) + { + switch (set) + { + case e_regSetGPR: + if (reg < k_num_gpr_registers) + { + ((uint32_t*)(&m_state.context.gpr))[reg] = value->value.uint32; + success = true; + } + break; + + case e_regSetFPU: + switch (reg) + { + case fpu_fcw: *((uint16_t *)(&m_state.context.fpu.__fpu_fcw)) = value->value.uint16; success = true; break; + case fpu_fsw: *((uint16_t *)(&m_state.context.fpu.__fpu_fsw)) = value->value.uint16; success = true; break; + case fpu_ftw: m_state.context.fpu.__fpu_ftw = value->value.uint8; success = true; break; + case fpu_fop: m_state.context.fpu.__fpu_fop = value->value.uint16; success = true; break; + case fpu_ip: m_state.context.fpu.__fpu_ip = value->value.uint32; success = true; break; + case fpu_cs: m_state.context.fpu.__fpu_cs = value->value.uint16; success = true; break; + case fpu_dp: m_state.context.fpu.__fpu_dp = value->value.uint32; success = true; break; + case fpu_ds: m_state.context.fpu.__fpu_ds = value->value.uint16; success = true; break; + case fpu_mxcsr: m_state.context.fpu.__fpu_mxcsr = value->value.uint32; success = true; break; + case fpu_mxcsrmask: m_state.context.fpu.__fpu_mxcsrmask = value->value.uint32; success = true; break; + + case fpu_stmm0: memcpy (m_state.context.fpu.__fpu_stmm0.__mmst_reg, &value->value.uint8, 10); success = true; break; + case fpu_stmm1: memcpy (m_state.context.fpu.__fpu_stmm1.__mmst_reg, &value->value.uint8, 10); success = true; break; + case fpu_stmm2: memcpy (m_state.context.fpu.__fpu_stmm2.__mmst_reg, &value->value.uint8, 10); success = true; break; + case fpu_stmm3: memcpy (m_state.context.fpu.__fpu_stmm3.__mmst_reg, &value->value.uint8, 10); success = true; break; + case fpu_stmm4: memcpy (m_state.context.fpu.__fpu_stmm4.__mmst_reg, &value->value.uint8, 10); success = true; break; + case fpu_stmm5: memcpy (m_state.context.fpu.__fpu_stmm5.__mmst_reg, &value->value.uint8, 10); success = true; break; + case fpu_stmm6: memcpy (m_state.context.fpu.__fpu_stmm6.__mmst_reg, &value->value.uint8, 10); success = true; break; + case fpu_stmm7: memcpy (m_state.context.fpu.__fpu_stmm7.__mmst_reg, &value->value.uint8, 10); success = true; break; + + case fpu_xmm0: memcpy(m_state.context.fpu.__fpu_xmm0.__xmm_reg, &value->value.uint8, 16); success = true; break; + case fpu_xmm1: memcpy(m_state.context.fpu.__fpu_xmm1.__xmm_reg, &value->value.uint8, 16); success = true; break; + case fpu_xmm2: memcpy(m_state.context.fpu.__fpu_xmm2.__xmm_reg, &value->value.uint8, 16); success = true; break; + case fpu_xmm3: memcpy(m_state.context.fpu.__fpu_xmm3.__xmm_reg, &value->value.uint8, 16); success = true; break; + case fpu_xmm4: memcpy(m_state.context.fpu.__fpu_xmm4.__xmm_reg, &value->value.uint8, 16); success = true; break; + case fpu_xmm5: memcpy(m_state.context.fpu.__fpu_xmm5.__xmm_reg, &value->value.uint8, 16); success = true; break; + case fpu_xmm6: memcpy(m_state.context.fpu.__fpu_xmm6.__xmm_reg, &value->value.uint8, 16); success = true; break; + case fpu_xmm7: memcpy(m_state.context.fpu.__fpu_xmm7.__xmm_reg, &value->value.uint8, 16); success = true; break; + } + break; + + case e_regSetEXC: + if (reg < k_num_exc_registers) + { + (&m_state.context.exc.__trapno)[reg] = value->value.uint32; + success = true; + } + break; + } + } + + if (success) + return SetRegisterState(set) == KERN_SUCCESS; + return false; +} + + +nub_size_t +DNBArchImplI386::GetRegisterContext (void *buf, nub_size_t buf_len) +{ + nub_size_t size = sizeof (m_state.context); + + if (buf && buf_len) + { + if (size > buf_len) + size = buf_len; + + bool force = false; + if (GetGPRState(force) | GetFPUState(force) | GetEXCState(force)) + return 0; + ::memcpy (buf, &m_state.context, size); + } + DNBLogThreadedIf (LOG_THREAD, "DNBArchImplI386::GetRegisterContext (buf = %p, len = %zu) => %zu", buf, buf_len, size); + // Return the size of the register context even if NULL was passed in + return size; +} + +nub_size_t +DNBArchImplI386::SetRegisterContext (const void *buf, nub_size_t buf_len) +{ + nub_size_t size = sizeof (m_state.context); + if (buf == NULL || buf_len == 0) + size = 0; + + if (size) + { + if (size > buf_len) + size = buf_len; + + ::memcpy (&m_state.context, buf, size); + SetGPRState(); + SetFPUState(); + SetEXCState(); + } + DNBLogThreadedIf (LOG_THREAD, "DNBArchImplI386::SetRegisterContext (buf = %p, len = %zu) => %zu", buf, buf_len, size); + return size; +} + + + +kern_return_t +DNBArchImplI386::GetRegisterState(int set, bool force) +{ + switch (set) + { + case e_regSetALL: return GetGPRState(force) | GetFPUState(force) | GetEXCState(force); + case e_regSetGPR: return GetGPRState(force); + case e_regSetFPU: return GetFPUState(force); + case e_regSetEXC: return GetEXCState(force); + default: break; + } + return KERN_INVALID_ARGUMENT; +} + +kern_return_t +DNBArchImplI386::SetRegisterState(int set) +{ + // Make sure we have a valid context to set. + if (RegisterSetStateIsValid(set)) + { + switch (set) + { + case e_regSetALL: return SetGPRState() | SetFPUState() | SetEXCState(); + case e_regSetGPR: return SetGPRState(); + case e_regSetFPU: return SetFPUState(); + case e_regSetEXC: return SetEXCState(); + default: break; + } + } + return KERN_INVALID_ARGUMENT; +} + +bool +DNBArchImplI386::RegisterSetStateIsValid (int set) const +{ + return m_state.RegsAreValid(set); +} + + + +#endif // #if defined (__i386__) diff --git a/lldb/tools/debugserver/source/MacOSX/i386/DNBArchImplI386.h b/lldb/tools/debugserver/source/MacOSX/i386/DNBArchImplI386.h new file mode 100644 index 00000000000..10972c24ca5 --- /dev/null +++ b/lldb/tools/debugserver/source/MacOSX/i386/DNBArchImplI386.h @@ -0,0 +1,196 @@ +//===-- DNBArchImplI386.h ---------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/25/07. +// +//===----------------------------------------------------------------------===// + +#ifndef __DNBArchImplI386_h__ +#define __DNBArchImplI386_h__ + +#if defined (__i386__) + +#include "DNBArch.h" +#include <mach/mach_types.h> +#include <mach/thread_status.h> + + +class MachThread; + +class DNBArchImplI386 : public DNBArchProtocol +{ +public: + DNBArchImplI386(MachThread *thread) : + m_thread(thread), + m_state() + { + } + virtual ~DNBArchImplI386() + { + } + + static const DNBRegisterSetInfo * + GetRegisterSetInfo(nub_size_t *num_reg_sets); + + virtual bool GetRegisterValue(int set, int reg, DNBRegisterValue *reg); + virtual bool SetRegisterValue(int set, int reg, const DNBRegisterValue *reg); + virtual nub_size_t GetRegisterContext (void *buf, nub_size_t buf_len); + virtual nub_size_t SetRegisterContext (const void *buf, nub_size_t buf_len); + virtual kern_return_t GetRegisterState (int set, bool force); + virtual kern_return_t SetRegisterState (int set); + virtual bool RegisterSetStateIsValid (int set) const; + + virtual uint64_t GetPC(uint64_t failValue); // Get program counter + virtual kern_return_t SetPC(uint64_t value); + virtual uint64_t GetSP(uint64_t failValue); // Get stack pointer + virtual void ThreadWillResume(); + virtual bool ThreadDidStop(); + virtual bool NotifyException(MachException::Data& exc); + + static const uint8_t * const SoftwareBreakpointOpcode (nub_size_t byte_size); + static uint32_t GetCPUType(); + +protected: + kern_return_t EnableHardwareSingleStep (bool enable); + + typedef i386_thread_state_t GPR; + typedef i386_float_state_t FPU; + typedef i386_exception_state_t EXC; + + static const DNBRegisterInfo g_gpr_registers[]; + static const DNBRegisterInfo g_fpu_registers[]; + static const DNBRegisterInfo g_exc_registers[]; + static const DNBRegisterSetInfo g_reg_sets[]; + static const size_t k_num_gpr_registers; + static const size_t k_num_fpu_registers; + static const size_t k_num_exc_registers; + static const size_t k_num_all_registers; + static const size_t k_num_register_sets; + + typedef enum RegisterSetTag + { + e_regSetALL = REGISTER_SET_ALL, + e_regSetGPR, + e_regSetFPU, + e_regSetEXC, + kNumRegisterSets + } RegisterSet; + + typedef enum RegisterSetWordSizeTag + { + e_regSetWordSizeGPR = i386_THREAD_STATE_COUNT, + e_regSetWordSizeFPR = i386_FLOAT_STATE_COUNT, + e_regSetWordSizeEXC = i386_EXCEPTION_STATE_COUNT + } RegisterSetWordSize; + + enum + { + Read = 0, + Write = 1, + kNumErrors = 2 + }; + + struct Context + { + i386_thread_state_t gpr; + i386_float_state_t fpu; + i386_exception_state_t exc; + }; + + struct State + { + Context context; + kern_return_t gpr_errs[2]; // Read/Write errors + kern_return_t fpu_errs[2]; // Read/Write errors + kern_return_t exc_errs[2]; // Read/Write errors + + State() + { + uint32_t i; + for (i=0; i<kNumErrors; i++) + { + gpr_errs[i] = -1; + fpu_errs[i] = -1; + exc_errs[i] = -1; + } + } + void InvalidateAllRegisterStates() + { + SetError (e_regSetALL, Read, -1); + } + kern_return_t GetError (int flavor, uint32_t err_idx) const + { + if (err_idx < kNumErrors) + { + switch (flavor) + { + // When getting all errors, just OR all values together to see if + // we got any kind of error. + case e_regSetALL: return gpr_errs[err_idx] | + fpu_errs[err_idx] | + exc_errs[err_idx]; + case e_regSetGPR: return gpr_errs[err_idx]; + case e_regSetFPU: return fpu_errs[err_idx]; + case e_regSetEXC: return exc_errs[err_idx]; + default: break; + } + } + return -1; + } + bool SetError (int flavor, uint32_t err_idx, kern_return_t err) + { + if (err_idx < kNumErrors) + { + switch (flavor) + { + case e_regSetALL: + gpr_errs[err_idx] = + fpu_errs[err_idx] = + exc_errs[err_idx] = err; + return true; + + case e_regSetGPR: + gpr_errs[err_idx] = err; + return true; + + case e_regSetFPU: + fpu_errs[err_idx] = err; + return true; + + case e_regSetEXC: + exc_errs[err_idx] = err; + return true; + + default: break; + } + } + return false; + } + bool RegsAreValid (int flavor) const + { + return GetError(flavor, Read) == KERN_SUCCESS; + } + }; + + kern_return_t GetGPRState (bool force); + kern_return_t GetFPUState (bool force); + kern_return_t GetEXCState (bool force); + + kern_return_t SetGPRState (); + kern_return_t SetFPUState (); + kern_return_t SetEXCState (); + + MachThread *m_thread; + State m_state; +}; + +typedef DNBArchImplI386 DNBArch; + +#endif // #if defined (__i386__) +#endif // #ifndef __DNBArchImplI386_h__ diff --git a/lldb/tools/debugserver/source/MacOSX/ppc/DNBArchImpl.cpp b/lldb/tools/debugserver/source/MacOSX/ppc/DNBArchImpl.cpp new file mode 100644 index 00000000000..abd1a353158 --- /dev/null +++ b/lldb/tools/debugserver/source/MacOSX/ppc/DNBArchImpl.cpp @@ -0,0 +1,569 @@ +//===-- DNBArchImpl.cpp -----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/25/07. +// +//===----------------------------------------------------------------------===// + +#if defined (__powerpc__) || defined (__ppc__) || defined (__ppc64__) + +#if __DARWIN_UNIX03 +#define PREFIX_DOUBLE_UNDERSCORE_DARWIN_UNIX03(reg) __##reg +#else +#define PREFIX_DOUBLE_UNDERSCORE_DARWIN_UNIX03(reg) reg +#endif + +#include "MacOSX/ppc/DNBArchImpl.h" +#include "MacOSX/MachThread.h" +#include "DNBBreakpoint.h" +#include "DNBLog.h" +#include "DNBRegisterInfo.h" + +static const uint8_t g_breakpoint_opcode[] = { 0x7F, 0xC0, 0x00, 0x08 }; + +const uint8_t * const +DNBArchMachPPC::SoftwareBreakpointOpcode (nub_size_t size) +{ + if (size == 4) + return g_breakpoint_opcode; + return NULL; +} + +uint32_t +DNBArchMachPPC::GetCPUType() +{ + return CPU_TYPE_POWERPC; +} + +uint64_t +DNBArchMachPPC::GetPC(uint64_t failValue) +{ + // Get program counter + if (GetGPRState(false) == KERN_SUCCESS) + return m_state.gpr.PREFIX_DOUBLE_UNDERSCORE_DARWIN_UNIX03(srr0); + return failValue; +} + +kern_return_t +DNBArchMachPPC::SetPC(uint64_t value) +{ + // Get program counter + kern_return_t err = GetGPRState(false); + if (err == KERN_SUCCESS) + { + m_state.gpr.PREFIX_DOUBLE_UNDERSCORE_DARWIN_UNIX03(srr0) = value; + err = SetGPRState(); + } + return err == KERN_SUCCESS; +} + +uint64_t +DNBArchMachPPC::GetSP(uint64_t failValue) +{ + // Get stack pointer + if (GetGPRState(false) == KERN_SUCCESS) + return m_state.gpr.PREFIX_DOUBLE_UNDERSCORE_DARWIN_UNIX03(r1); + return failValue; +} + +kern_return_t +DNBArchMachPPC::GetGPRState(bool force) +{ + if (force || m_state.GetError(e_regSetGPR, Read)) + { + mach_msg_type_number_t count = e_regSetWordSizeGPR; + m_state.SetError(e_regSetGPR, Read, ::thread_get_state(m_thread->ThreadID(), e_regSetGPR, (thread_state_t)&m_state.gpr, &count)); + } + return m_state.GetError(e_regSetGPR, Read); +} + +kern_return_t +DNBArchMachPPC::GetFPRState(bool force) +{ + if (force || m_state.GetError(e_regSetFPR, Read)) + { + mach_msg_type_number_t count = e_regSetWordSizeFPR; + m_state.SetError(e_regSetFPR, Read, ::thread_get_state(m_thread->ThreadID(), e_regSetFPR, (thread_state_t)&m_state.fpr, &count)); + } + return m_state.GetError(e_regSetFPR, Read); +} + +kern_return_t +DNBArchMachPPC::GetEXCState(bool force) +{ + if (force || m_state.GetError(e_regSetEXC, Read)) + { + mach_msg_type_number_t count = e_regSetWordSizeEXC; + m_state.SetError(e_regSetEXC, Read, ::thread_get_state(m_thread->ThreadID(), e_regSetEXC, (thread_state_t)&m_state.exc, &count)); + } + return m_state.GetError(e_regSetEXC, Read); +} + +kern_return_t +DNBArchMachPPC::GetVECState(bool force) +{ + if (force || m_state.GetError(e_regSetVEC, Read)) + { + mach_msg_type_number_t count = e_regSetWordSizeVEC; + m_state.SetError(e_regSetVEC, Read, ::thread_get_state(m_thread->ThreadID(), e_regSetVEC, (thread_state_t)&m_state.vec, &count)); + } + return m_state.GetError(e_regSetVEC, Read); +} + +kern_return_t +DNBArchMachPPC::SetGPRState() +{ + m_state.SetError(e_regSetGPR, Write, ::thread_set_state(m_thread->ThreadID(), e_regSetGPR, (thread_state_t)&m_state.gpr, e_regSetWordSizeGPR)); + return m_state.GetError(e_regSetGPR, Write); +} + +kern_return_t +DNBArchMachPPC::SetFPRState() +{ + m_state.SetError(e_regSetFPR, Write, ::thread_set_state(m_thread->ThreadID(), e_regSetFPR, (thread_state_t)&m_state.fpr, e_regSetWordSizeFPR)); + return m_state.GetError(e_regSetFPR, Write); +} + +kern_return_t +DNBArchMachPPC::SetEXCState() +{ + m_state.SetError(e_regSetEXC, Write, ::thread_set_state(m_thread->ThreadID(), e_regSetEXC, (thread_state_t)&m_state.exc, e_regSetWordSizeEXC)); + return m_state.GetError(e_regSetEXC, Write); +} + +kern_return_t +DNBArchMachPPC::SetVECState() +{ + m_state.SetError(e_regSetVEC, Write, ::thread_set_state(m_thread->ThreadID(), e_regSetVEC, (thread_state_t)&m_state.vec, e_regSetWordSizeVEC)); + return m_state.GetError(e_regSetVEC, Write); +} + +bool +DNBArchMachPPC::ThreadWillResume() +{ + bool success = true; + + // Do we need to step this thread? If so, let the mach thread tell us so. + if (m_thread->IsStepping()) + { + // This is the primary thread, let the arch do anything it needs + success = EnableHardwareSingleStep(true) == KERN_SUCCESS; + } + return success; +} + +bool +DNBArchMachPPC::ThreadDidStop() +{ + bool success = true; + + m_state.InvalidateAllRegisterStates(); + + // Are we stepping a single instruction? + if (GetGPRState(true) == KERN_SUCCESS) + { + // We are single stepping, was this the primary thread? + if (m_thread->IsStepping()) + { + // This was the primary thread, we need to clear the trace + // bit if so. + success = EnableHardwareSingleStep(false) == KERN_SUCCESS; + } + else + { + // The MachThread will automatically restore the suspend count + // in ThreadDidStop(), so we don't need to do anything here if + // we weren't the primary thread the last time + } + } + return success; +} + + +// Set the single step bit in the processor status register. +kern_return_t +DNBArchMachPPC::EnableHardwareSingleStep (bool enable) +{ + DNBLogThreadedIf(LOG_STEP, "DNBArchMachPPC::EnableHardwareSingleStep( enable = %d )", enable); + if (GetGPRState(false) == KERN_SUCCESS) + { + const uint32_t trace_bit = 0x400; + if (enable) + m_state.gpr.PREFIX_DOUBLE_UNDERSCORE_DARWIN_UNIX03(srr1) |= trace_bit; + else + m_state.gpr.PREFIX_DOUBLE_UNDERSCORE_DARWIN_UNIX03(srr1) &= ~trace_bit; + return SetGPRState(); + } + return m_state.GetError(e_regSetGPR, Read); +} + +//---------------------------------------------------------------------- +// Register information defintions for 32 bit PowerPC. +//---------------------------------------------------------------------- + +enum gpr_regnums +{ + e_regNumGPR_srr0, + e_regNumGPR_srr1, + e_regNumGPR_r0, + e_regNumGPR_r1, + e_regNumGPR_r2, + e_regNumGPR_r3, + e_regNumGPR_r4, + e_regNumGPR_r5, + e_regNumGPR_r6, + e_regNumGPR_r7, + e_regNumGPR_r8, + e_regNumGPR_r9, + e_regNumGPR_r10, + e_regNumGPR_r11, + e_regNumGPR_r12, + e_regNumGPR_r13, + e_regNumGPR_r14, + e_regNumGPR_r15, + e_regNumGPR_r16, + e_regNumGPR_r17, + e_regNumGPR_r18, + e_regNumGPR_r19, + e_regNumGPR_r20, + e_regNumGPR_r21, + e_regNumGPR_r22, + e_regNumGPR_r23, + e_regNumGPR_r24, + e_regNumGPR_r25, + e_regNumGPR_r26, + e_regNumGPR_r27, + e_regNumGPR_r28, + e_regNumGPR_r29, + e_regNumGPR_r30, + e_regNumGPR_r31, + e_regNumGPR_cr, + e_regNumGPR_xer, + e_regNumGPR_lr, + e_regNumGPR_ctr, + e_regNumGPR_mq, + e_regNumGPR_vrsave +}; + + + + +// General purpose registers +static DNBRegisterInfo g_gpr_registers[] = +{ + { "srr0" , Uint, 4, Hex }, + { "srr1" , Uint, 4, Hex }, + { "r0" , Uint, 4, Hex }, + { "r1" , Uint, 4, Hex }, + { "r2" , Uint, 4, Hex }, + { "r3" , Uint, 4, Hex }, + { "r4" , Uint, 4, Hex }, + { "r5" , Uint, 4, Hex }, + { "r6" , Uint, 4, Hex }, + { "r7" , Uint, 4, Hex }, + { "r8" , Uint, 4, Hex }, + { "r9" , Uint, 4, Hex }, + { "r10" , Uint, 4, Hex }, + { "r11" , Uint, 4, Hex }, + { "r12" , Uint, 4, Hex }, + { "r13" , Uint, 4, Hex }, + { "r14" , Uint, 4, Hex }, + { "r15" , Uint, 4, Hex }, + { "r16" , Uint, 4, Hex }, + { "r17" , Uint, 4, Hex }, + { "r18" , Uint, 4, Hex }, + { "r19" , Uint, 4, Hex }, + { "r20" , Uint, 4, Hex }, + { "r21" , Uint, 4, Hex }, + { "r22" , Uint, 4, Hex }, + { "r23" , Uint, 4, Hex }, + { "r24" , Uint, 4, Hex }, + { "r25" , Uint, 4, Hex }, + { "r26" , Uint, 4, Hex }, + { "r27" , Uint, 4, Hex }, + { "r28" , Uint, 4, Hex }, + { "r29" , Uint, 4, Hex }, + { "r30" , Uint, 4, Hex }, + { "r31" , Uint, 4, Hex }, + { "cr" , Uint, 4, Hex }, + { "xer" , Uint, 4, Hex }, + { "lr" , Uint, 4, Hex }, + { "ctr" , Uint, 4, Hex }, + { "mq" , Uint, 4, Hex }, + { "vrsave", Uint, 4, Hex }, +}; + +// Floating point registers +static DNBRegisterInfo g_fpr_registers[] = +{ + { "fp0" , IEEE754, 8, Float }, + { "fp1" , IEEE754, 8, Float }, + { "fp2" , IEEE754, 8, Float }, + { "fp3" , IEEE754, 8, Float }, + { "fp4" , IEEE754, 8, Float }, + { "fp5" , IEEE754, 8, Float }, + { "fp6" , IEEE754, 8, Float }, + { "fp7" , IEEE754, 8, Float }, + { "fp8" , IEEE754, 8, Float }, + { "fp9" , IEEE754, 8, Float }, + { "fp10" , IEEE754, 8, Float }, + { "fp11" , IEEE754, 8, Float }, + { "fp12" , IEEE754, 8, Float }, + { "fp13" , IEEE754, 8, Float }, + { "fp14" , IEEE754, 8, Float }, + { "fp15" , IEEE754, 8, Float }, + { "fp16" , IEEE754, 8, Float }, + { "fp17" , IEEE754, 8, Float }, + { "fp18" , IEEE754, 8, Float }, + { "fp19" , IEEE754, 8, Float }, + { "fp20" , IEEE754, 8, Float }, + { "fp21" , IEEE754, 8, Float }, + { "fp22" , IEEE754, 8, Float }, + { "fp23" , IEEE754, 8, Float }, + { "fp24" , IEEE754, 8, Float }, + { "fp25" , IEEE754, 8, Float }, + { "fp26" , IEEE754, 8, Float }, + { "fp27" , IEEE754, 8, Float }, + { "fp28" , IEEE754, 8, Float }, + { "fp29" , IEEE754, 8, Float }, + { "fp30" , IEEE754, 8, Float }, + { "fp31" , IEEE754, 8, Float }, + { "fpscr" , Uint, 4, Hex } +}; + +// Exception registers + +static DNBRegisterInfo g_exc_registers[] = +{ + { "dar" , Uint, 4, Hex }, + { "dsisr" , Uint, 4, Hex }, + { "exception" , Uint, 4, Hex } +}; + +// Altivec registers +static DNBRegisterInfo g_vec_registers[] = +{ + { "vr0" , Vector, 16, VectorOfFloat32 }, + { "vr1" , Vector, 16, VectorOfFloat32 }, + { "vr2" , Vector, 16, VectorOfFloat32 }, + { "vr3" , Vector, 16, VectorOfFloat32 }, + { "vr4" , Vector, 16, VectorOfFloat32 }, + { "vr5" , Vector, 16, VectorOfFloat32 }, + { "vr6" , Vector, 16, VectorOfFloat32 }, + { "vr7" , Vector, 16, VectorOfFloat32 }, + { "vr8" , Vector, 16, VectorOfFloat32 }, + { "vr9" , Vector, 16, VectorOfFloat32 }, + { "vr10" , Vector, 16, VectorOfFloat32 }, + { "vr11" , Vector, 16, VectorOfFloat32 }, + { "vr12" , Vector, 16, VectorOfFloat32 }, + { "vr13" , Vector, 16, VectorOfFloat32 }, + { "vr14" , Vector, 16, VectorOfFloat32 }, + { "vr15" , Vector, 16, VectorOfFloat32 }, + { "vr16" , Vector, 16, VectorOfFloat32 }, + { "vr17" , Vector, 16, VectorOfFloat32 }, + { "vr18" , Vector, 16, VectorOfFloat32 }, + { "vr19" , Vector, 16, VectorOfFloat32 }, + { "vr20" , Vector, 16, VectorOfFloat32 }, + { "vr21" , Vector, 16, VectorOfFloat32 }, + { "vr22" , Vector, 16, VectorOfFloat32 }, + { "vr23" , Vector, 16, VectorOfFloat32 }, + { "vr24" , Vector, 16, VectorOfFloat32 }, + { "vr25" , Vector, 16, VectorOfFloat32 }, + { "vr26" , Vector, 16, VectorOfFloat32 }, + { "vr27" , Vector, 16, VectorOfFloat32 }, + { "vr28" , Vector, 16, VectorOfFloat32 }, + { "vr29" , Vector, 16, VectorOfFloat32 }, + { "vr30" , Vector, 16, VectorOfFloat32 }, + { "vr31" , Vector, 16, VectorOfFloat32 }, + { "vscr" , Uint, 16, Hex }, + { "vrvalid" , Uint, 4, Hex } +}; + +// Number of registers in each register set +const size_t k_num_gpr_registers = sizeof(g_gpr_registers)/sizeof(DNBRegisterInfo); +const size_t k_num_fpr_registers = sizeof(g_fpr_registers)/sizeof(DNBRegisterInfo); +const size_t k_num_exc_registers = sizeof(g_exc_registers)/sizeof(DNBRegisterInfo); +const size_t k_num_vec_registers = sizeof(g_vec_registers)/sizeof(DNBRegisterInfo); +// Total number of registers for this architecture +const size_t k_num_ppc_registers = k_num_gpr_registers + k_num_fpr_registers + k_num_exc_registers + k_num_vec_registers; + +//---------------------------------------------------------------------- +// Register set definitions. The first definitions at register set index +// of zero is for all registers, followed by other registers sets. The +// register information for the all register set need not be filled in. +//---------------------------------------------------------------------- +static const DNBRegisterSetInfo g_reg_sets[] = +{ + { "PowerPC Registers", NULL, k_num_ppc_registers }, + { "General Purpose Registers", g_gpr_registers, k_num_gpr_registers }, + { "Floating Point Registers", g_fpr_registers, k_num_fpr_registers }, + { "Exception State Registers", g_exc_registers, k_num_exc_registers }, + { "Altivec Registers", g_vec_registers, k_num_vec_registers } +}; +// Total number of register sets for this architecture +const size_t k_num_register_sets = sizeof(g_reg_sets)/sizeof(DNBRegisterSetInfo); + + +const DNBRegisterSetInfo * +DNBArchMachPPC::GetRegisterSetInfo(nub_size_t *num_reg_sets) const +{ + *num_reg_sets = k_num_register_sets; + return g_reg_sets; +} + +bool +DNBArchMachPPC::GetRegisterValue(int set, int reg, DNBRegisterValue *value) const +{ + if (set == REGISTER_SET_GENERIC) + { + switch (reg) + { + case GENERIC_REGNUM_PC: // Program Counter + set = e_regSetGPR; + reg = e_regNumGPR_srr0; + break; + + case GENERIC_REGNUM_SP: // Stack Pointer + set = e_regSetGPR; + reg = e_regNumGPR_r1; + break; + + case GENERIC_REGNUM_FP: // Frame Pointer + // Return false for now instead of returning r30 as gcc 3.x would + // use a variety of registers for the FP and it takes inspecting + // the stack to make sure there is a frame pointer before we can + // determine the FP. + return false; + + case GENERIC_REGNUM_RA: // Return Address + set = e_regSetGPR; + reg = e_regNumGPR_lr; + break; + + case GENERIC_REGNUM_FLAGS: // Processor flags register + set = e_regSetGPR; + reg = e_regNumGPR_srr1; + break; + + default: + return false; + } + } + + if (!m_state.RegsAreValid(set)) + return false; + + const DNBRegisterInfo *regInfo = m_thread->GetRegisterInfo(set, reg); + if (regInfo) + { + value->info = *regInfo; + switch (set) + { + case e_regSetGPR: + if (reg < k_num_gpr_registers) + { + value->value.uint32 = (&m_state.gpr.PREFIX_DOUBLE_UNDERSCORE_DARWIN_UNIX03(srr0))[reg]; + return true; + } + break; + + case e_regSetFPR: + if (reg < 32) + { + value->value.float64 = m_state.fpr.PREFIX_DOUBLE_UNDERSCORE_DARWIN_UNIX03(fpregs)[reg]; + return true; + } + else if (reg == 32) + { + value->value.uint32 = m_state.fpr.PREFIX_DOUBLE_UNDERSCORE_DARWIN_UNIX03(fpscr); + return true; + } + break; + + case e_regSetEXC: + if (reg < k_num_exc_registers) + { + value->value.uint32 = (&m_state.exc.PREFIX_DOUBLE_UNDERSCORE_DARWIN_UNIX03(dar))[reg]; + return true; + } + break; + + case e_regSetVEC: + if (reg < k_num_vec_registers) + { + if (reg < 33) // FP0 - FP31 and VSCR + { + // Copy all 4 uint32 values for this vector register + value->value.v_uint32[0] = m_state.vec.PREFIX_DOUBLE_UNDERSCORE_DARWIN_UNIX03(save_vr)[reg][0]; + value->value.v_uint32[1] = m_state.vec.PREFIX_DOUBLE_UNDERSCORE_DARWIN_UNIX03(save_vr)[reg][1]; + value->value.v_uint32[2] = m_state.vec.PREFIX_DOUBLE_UNDERSCORE_DARWIN_UNIX03(save_vr)[reg][2]; + value->value.v_uint32[3] = m_state.vec.PREFIX_DOUBLE_UNDERSCORE_DARWIN_UNIX03(save_vr)[reg][3]; + return true; + } + else if (reg == 34) // VRVALID + { + value->value.uint32 = m_state.vec.PREFIX_DOUBLE_UNDERSCORE_DARWIN_UNIX03(save_vrvalid); + return true; + } + } + break; + } + } + return false; +} + + +kern_return_t +DNBArchMachPPC::GetRegisterState(int set, bool force) +{ + switch (set) + { + case e_regSetALL: + return GetGPRState(force) | + GetFPRState(force) | + GetEXCState(force) | + GetVECState(force); + case e_regSetGPR: return GetGPRState(force); + case e_regSetFPR: return GetFPRState(force); + case e_regSetEXC: return GetEXCState(force); + case e_regSetVEC: return GetVECState(force); + default: break; + } + return KERN_INVALID_ARGUMENT; +} + +kern_return_t +DNBArchMachPPC::SetRegisterState(int set) +{ + // Make sure we have a valid context to set. + kern_return_t err = GetRegisterState(set, false); + if (err != KERN_SUCCESS) + return err; + + switch (set) + { + case e_regSetALL: return SetGPRState() | SetFPRState() | SetEXCState() | SetVECState(); + case e_regSetGPR: return SetGPRState(); + case e_regSetFPR: return SetFPRState(); + case e_regSetEXC: return SetEXCState(); + case e_regSetVEC: return SetVECState(); + default: break; + } + return KERN_INVALID_ARGUMENT; +} + +bool +DNBArchMachPPC::RegisterSetStateIsValid (int set) const +{ + return m_state.RegsAreValid(set); +} + + +#endif // #if defined (__powerpc__) || defined (__ppc__) || defined (__ppc64__) + diff --git a/lldb/tools/debugserver/source/MacOSX/ppc/DNBArchImpl.h b/lldb/tools/debugserver/source/MacOSX/ppc/DNBArchImpl.h new file mode 100644 index 00000000000..6263407460a --- /dev/null +++ b/lldb/tools/debugserver/source/MacOSX/ppc/DNBArchImpl.h @@ -0,0 +1,180 @@ +//===-- DNBArchImpl.h -------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/25/07. +// +//===----------------------------------------------------------------------===// + +#ifndef __DebugNubArchMachPPC_h__ +#define __DebugNubArchMachPPC_h__ + +#if defined (__powerpc__) || defined (__ppc__) || defined (__ppc64__) + +#include "DNBArch.h" + +class MachThread; + +class DNBArchMachPPC : public DNBArchProtocol +{ +public: + DNBArchMachPPC(MachThread *thread) : + m_thread(thread), + m_state() + { + } + + virtual ~DNBArchMachPPC() + { + } + + virtual const DNBRegisterSetInfo * + GetRegisterSetInfo(nub_size_t *num_reg_sets) const; + virtual bool GetRegisterValue(int set, int reg, DNBRegisterValue *value) const; + virtual kern_return_t GetRegisterState (int set, bool force); + virtual kern_return_t SetRegisterState (int set); + virtual bool RegisterSetStateIsValid (int set) const; + + virtual uint64_t GetPC(uint64_t failValue); // Get program counter + virtual kern_return_t SetPC(uint64_t value); + virtual uint64_t GetSP(uint64_t failValue); // Get stack pointer + virtual bool ThreadWillResume(); + virtual bool ThreadDidStop(); + + static const uint8_t * const SoftwareBreakpointOpcode (nub_size_t byte_size); + static uint32_t GetCPUType(); + +protected: + + + kern_return_t EnableHardwareSingleStep (bool enable); + + typedef enum RegisterSetTag + { + e_regSetALL = REGISTER_SET_ALL, + e_regSetGPR, + e_regSetFPR, + e_regSetEXC, + e_regSetVEC, + kNumRegisterSets + } RegisterSet; + + typedef enum RegisterSetWordSizeTag + { + e_regSetWordSizeGPR = PPC_THREAD_STATE_COUNT, + e_regSetWordSizeFPR = PPC_FLOAT_STATE_COUNT, + e_regSetWordSizeEXC = PPC_EXCEPTION_STATE_COUNT, + e_regSetWordSizeVEC = PPC_VECTOR_STATE_COUNT + } RegisterSetWordSize; + + enum + { + Read = 0, + Write = 1, + kNumErrors = 2 + }; + + struct State + { + ppc_thread_state_t gpr; + ppc_float_state_t fpr; + ppc_exception_state_t exc; + ppc_vector_state_t vec; + kern_return_t gpr_errs[2]; // Read/Write errors + kern_return_t fpr_errs[2]; // Read/Write errors + kern_return_t exc_errs[2]; // Read/Write errors + kern_return_t vec_errs[2]; // Read/Write errors + + State() + { + uint32_t i; + for (i=0; i<kNumErrors; i++) + { + gpr_errs[i] = -1; + fpr_errs[i] = -1; + exc_errs[i] = -1; + vec_errs[i] = -1; + } + } + void InvalidateAllRegisterStates() + { + SetError (e_regSetALL, Read, -1); + } + kern_return_t GetError (int set, uint32_t err_idx) const + { + if (err_idx < kNumErrors) + { + switch (set) + { + // When getting all errors, just OR all values together to see if + // we got any kind of error. + case e_regSetALL: return gpr_errs[err_idx] | fpr_errs[err_idx] | exc_errs[err_idx] | vec_errs[err_idx]; + case e_regSetGPR: return gpr_errs[err_idx]; + case e_regSetFPR: return fpr_errs[err_idx]; + case e_regSetEXC: return exc_errs[err_idx]; + case e_regSetVEC: return vec_errs[err_idx]; + default: break; + } + } + return -1; + } + bool SetError (int set, uint32_t err_idx, kern_return_t err) + { + if (err_idx < kNumErrors) + { + switch (set) + { + case e_regSetALL: + gpr_errs[err_idx] = fpr_errs[err_idx] = exc_errs[err_idx] = vec_errs[err_idx] = err; + return true; + + case e_regSetGPR: + gpr_errs[err_idx] = err; + return true; + + case e_regSetFPR: + fpr_errs[err_idx] = err; + return true; + + case e_regSetEXC: + exc_errs[err_idx] = err; + return true; + + case e_regSetVEC: + vec_errs[err_idx] = err; + return true; + + default: break; + } + } + return false; + } + bool RegsAreValid (int set) const + { + return GetError(set, Read) == KERN_SUCCESS; + } + }; + + kern_return_t GetGPRState (bool force); + kern_return_t GetFPRState (bool force); + kern_return_t GetEXCState (bool force); + kern_return_t GetVECState (bool force); + + kern_return_t SetGPRState (); + kern_return_t SetFPRState (); + kern_return_t SetEXCState (); + kern_return_t SetVECState (); + +protected: + MachThread * m_thread; + State m_state; +}; + +typedef DNBArchMachPPC DNBArch; +#endif // #if defined (__powerpc__) || defined (__ppc__) || defined (__ppc64__) +#endif // #ifndef __DebugNubArchMachPPC_h__ diff --git a/lldb/tools/debugserver/source/MacOSX/x86_64/DNBArchImplX86_64.cpp b/lldb/tools/debugserver/source/MacOSX/x86_64/DNBArchImplX86_64.cpp new file mode 100644 index 00000000000..1f18156650c --- /dev/null +++ b/lldb/tools/debugserver/source/MacOSX/x86_64/DNBArchImplX86_64.cpp @@ -0,0 +1,1035 @@ +//===-- DNBArchImplX86_64.cpp -----------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/25/07. +// +//===----------------------------------------------------------------------===// + +#if defined (__x86_64__) + +#include <sys/cdefs.h> + +#include "MacOSX/x86_64/DNBArchImplX86_64.h" +#include "DNBLog.h" +#include "MachThread.h" +#include "MachProcess.h" + +static const uint8_t g_breakpoint_opcode[] = { 0xCC }; + +const uint8_t * const +DNBArchImplX86_64::SoftwareBreakpointOpcode (nub_size_t byte_size) +{ + if (byte_size == 1) + return g_breakpoint_opcode; + return NULL; +} + +uint32_t +DNBArchImplX86_64::GetCPUType() +{ + return CPU_TYPE_X86_64; +} + +uint64_t +DNBArchImplX86_64::GetPC(uint64_t failValue) +{ + // Get program counter + if (GetGPRState(false) == KERN_SUCCESS) + return m_state.context.gpr.__rip; + return failValue; +} + +kern_return_t +DNBArchImplX86_64::SetPC(uint64_t value) +{ + // Get program counter + kern_return_t err = GetGPRState(false); + if (err == KERN_SUCCESS) + { + m_state.context.gpr.__rip = value; + err = SetGPRState(); + } + return err == KERN_SUCCESS; +} + +uint64_t +DNBArchImplX86_64::GetSP(uint64_t failValue) +{ + // Get stack pointer + if (GetGPRState(false) == KERN_SUCCESS) + return m_state.context.gpr.__rsp; + return failValue; +} + +// Uncomment the value below to verify the values in the debugger. +//#define DEBUG_GPR_VALUES 1 // DO NOT CHECK IN WITH THIS DEFINE ENABLED + +kern_return_t +DNBArchImplX86_64::GetGPRState(bool force) +{ + if (force || m_state.GetError(e_regSetGPR, Read)) + { +#if DEBUG_GPR_VALUES + m_state.context.gpr.__rax = ('a' << 8) + 'x'; + m_state.context.gpr.__rbx = ('b' << 8) + 'x'; + m_state.context.gpr.__rcx = ('c' << 8) + 'x'; + m_state.context.gpr.__rdx = ('d' << 8) + 'x'; + m_state.context.gpr.__rdi = ('d' << 8) + 'i'; + m_state.context.gpr.__rsi = ('s' << 8) + 'i'; + m_state.context.gpr.__rbp = ('b' << 8) + 'p'; + m_state.context.gpr.__rsp = ('s' << 8) + 'p'; + m_state.context.gpr.__r8 = ('r' << 8) + '8'; + m_state.context.gpr.__r9 = ('r' << 8) + '9'; + m_state.context.gpr.__r10 = ('r' << 8) + 'a'; + m_state.context.gpr.__r11 = ('r' << 8) + 'b'; + m_state.context.gpr.__r12 = ('r' << 8) + 'c'; + m_state.context.gpr.__r13 = ('r' << 8) + 'd'; + m_state.context.gpr.__r14 = ('r' << 8) + 'e'; + m_state.context.gpr.__r15 = ('r' << 8) + 'f'; + m_state.context.gpr.__rip = ('i' << 8) + 'p'; + m_state.context.gpr.__rflags = ('f' << 8) + 'l'; + m_state.context.gpr.__cs = ('c' << 8) + 's'; + m_state.context.gpr.__fs = ('f' << 8) + 's'; + m_state.context.gpr.__gs = ('g' << 8) + 's'; + m_state.SetError(e_regSetGPR, Read, 0); +#else + mach_msg_type_number_t count = x86_THREAD_STATE64_COUNT; + m_state.SetError(e_regSetGPR, Read, ::thread_get_state(m_thread->ThreadID(), x86_THREAD_STATE64, (thread_state_t)&m_state.context.gpr, &count)); + DNBLogThreadedIf (LOG_THREAD, "::thread_get_state (0x%4.4x, %u, &gpr, %u) => 0x%8.8x" + "\n\trax = %16.16llx rbx = %16.16llx rcx = %16.16llx rdx = %16.16llx" + "\n\trdi = %16.16llx rsi = %16.16llx rbp = %16.16llx rsp = %16.16llx" + "\n\t r8 = %16.16llx r9 = %16.16llx r10 = %16.16llx r11 = %16.16llx" + "\n\tr12 = %16.16llx r13 = %16.16llx r14 = %16.16llx r15 = %16.16llx" + "\n\trip = %16.16llx" + "\n\tflg = %16.16llx cs = %16.16llx fs = %16.16llx gs = %16.16llx", + m_thread->ThreadID(), x86_THREAD_STATE64, x86_THREAD_STATE64_COUNT, + m_state.GetError(e_regSetGPR, Read), + m_state.context.gpr.__rax,m_state.context.gpr.__rbx,m_state.context.gpr.__rcx, + m_state.context.gpr.__rdx,m_state.context.gpr.__rdi,m_state.context.gpr.__rsi, + m_state.context.gpr.__rbp,m_state.context.gpr.__rsp,m_state.context.gpr.__r8, + m_state.context.gpr.__r9, m_state.context.gpr.__r10,m_state.context.gpr.__r11, + m_state.context.gpr.__r12,m_state.context.gpr.__r13,m_state.context.gpr.__r14, + m_state.context.gpr.__r15,m_state.context.gpr.__rip,m_state.context.gpr.__rflags, + m_state.context.gpr.__cs,m_state.context.gpr.__fs, m_state.context.gpr.__gs); + + // DNBLogThreadedIf (LOG_THREAD, "thread_get_state(0x%4.4x, %u, &gpr, %u) => 0x%8.8x" + // "\n\trax = %16.16llx" + // "\n\trbx = %16.16llx" + // "\n\trcx = %16.16llx" + // "\n\trdx = %16.16llx" + // "\n\trdi = %16.16llx" + // "\n\trsi = %16.16llx" + // "\n\trbp = %16.16llx" + // "\n\trsp = %16.16llx" + // "\n\t r8 = %16.16llx" + // "\n\t r9 = %16.16llx" + // "\n\tr10 = %16.16llx" + // "\n\tr11 = %16.16llx" + // "\n\tr12 = %16.16llx" + // "\n\tr13 = %16.16llx" + // "\n\tr14 = %16.16llx" + // "\n\tr15 = %16.16llx" + // "\n\trip = %16.16llx" + // "\n\tflg = %16.16llx" + // "\n\t cs = %16.16llx" + // "\n\t fs = %16.16llx" + // "\n\t gs = %16.16llx", + // m_thread->ThreadID(), + // x86_THREAD_STATE64, + // x86_THREAD_STATE64_COUNT, + // m_state.GetError(e_regSetGPR, Read), + // m_state.context.gpr.__rax, + // m_state.context.gpr.__rbx, + // m_state.context.gpr.__rcx, + // m_state.context.gpr.__rdx, + // m_state.context.gpr.__rdi, + // m_state.context.gpr.__rsi, + // m_state.context.gpr.__rbp, + // m_state.context.gpr.__rsp, + // m_state.context.gpr.__r8, + // m_state.context.gpr.__r9, + // m_state.context.gpr.__r10, + // m_state.context.gpr.__r11, + // m_state.context.gpr.__r12, + // m_state.context.gpr.__r13, + // m_state.context.gpr.__r14, + // m_state.context.gpr.__r15, + // m_state.context.gpr.__rip, + // m_state.context.gpr.__rflags, + // m_state.context.gpr.__cs, + // m_state.context.gpr.__fs, + // m_state.context.gpr.__gs); +#endif + } + return m_state.GetError(e_regSetGPR, Read); +} + +// Uncomment the value below to verify the values in the debugger. +//#define DEBUG_FPU_VALUES 1 // DO NOT CHECK IN WITH THIS DEFINE ENABLED + +kern_return_t +DNBArchImplX86_64::GetFPUState(bool force) +{ + if (force || m_state.GetError(e_regSetFPU, Read)) + { +#if DEBUG_FPU_VALUES + m_state.context.fpu.__fpu_reserved[0] = -1; + m_state.context.fpu.__fpu_reserved[1] = -1; + *(uint16_t *)&(m_state.context.fpu.__fpu_fcw) = 0x1234; + *(uint16_t *)&(m_state.context.fpu.__fpu_fsw) = 0x5678; + m_state.context.fpu.__fpu_ftw = 1; + m_state.context.fpu.__fpu_rsrv1 = UINT8_MAX; + m_state.context.fpu.__fpu_fop = 2; + m_state.context.fpu.__fpu_ip = 3; + m_state.context.fpu.__fpu_cs = 4; + m_state.context.fpu.__fpu_rsrv2 = 5; + m_state.context.fpu.__fpu_dp = 6; + m_state.context.fpu.__fpu_ds = 7; + m_state.context.fpu.__fpu_rsrv3 = UINT16_MAX; + m_state.context.fpu.__fpu_mxcsr = 8; + m_state.context.fpu.__fpu_mxcsrmask = 9; + int i; + for (i=0; i<16; ++i) + { + if (i<10) + { + m_state.context.fpu.__fpu_stmm0.__mmst_reg[i] = 'a'; + m_state.context.fpu.__fpu_stmm1.__mmst_reg[i] = 'b'; + m_state.context.fpu.__fpu_stmm2.__mmst_reg[i] = 'c'; + m_state.context.fpu.__fpu_stmm3.__mmst_reg[i] = 'd'; + m_state.context.fpu.__fpu_stmm4.__mmst_reg[i] = 'e'; + m_state.context.fpu.__fpu_stmm5.__mmst_reg[i] = 'f'; + m_state.context.fpu.__fpu_stmm6.__mmst_reg[i] = 'g'; + m_state.context.fpu.__fpu_stmm7.__mmst_reg[i] = 'h'; + } + else + { + m_state.context.fpu.__fpu_stmm0.__mmst_reg[i] = INT8_MIN; + m_state.context.fpu.__fpu_stmm1.__mmst_reg[i] = INT8_MIN; + m_state.context.fpu.__fpu_stmm2.__mmst_reg[i] = INT8_MIN; + m_state.context.fpu.__fpu_stmm3.__mmst_reg[i] = INT8_MIN; + m_state.context.fpu.__fpu_stmm4.__mmst_reg[i] = INT8_MIN; + m_state.context.fpu.__fpu_stmm5.__mmst_reg[i] = INT8_MIN; + m_state.context.fpu.__fpu_stmm6.__mmst_reg[i] = INT8_MIN; + m_state.context.fpu.__fpu_stmm7.__mmst_reg[i] = INT8_MIN; + } + + m_state.context.fpu.__fpu_xmm0.__xmm_reg[i] = '0'; + m_state.context.fpu.__fpu_xmm1.__xmm_reg[i] = '1'; + m_state.context.fpu.__fpu_xmm2.__xmm_reg[i] = '2'; + m_state.context.fpu.__fpu_xmm3.__xmm_reg[i] = '3'; + m_state.context.fpu.__fpu_xmm4.__xmm_reg[i] = '4'; + m_state.context.fpu.__fpu_xmm5.__xmm_reg[i] = '5'; + m_state.context.fpu.__fpu_xmm6.__xmm_reg[i] = '6'; + m_state.context.fpu.__fpu_xmm7.__xmm_reg[i] = '7'; + m_state.context.fpu.__fpu_xmm8.__xmm_reg[i] = '8'; + m_state.context.fpu.__fpu_xmm9.__xmm_reg[i] = '9'; + m_state.context.fpu.__fpu_xmm10.__xmm_reg[i] = 'A'; + m_state.context.fpu.__fpu_xmm11.__xmm_reg[i] = 'B'; + m_state.context.fpu.__fpu_xmm12.__xmm_reg[i] = 'C'; + m_state.context.fpu.__fpu_xmm13.__xmm_reg[i] = 'D'; + m_state.context.fpu.__fpu_xmm14.__xmm_reg[i] = 'E'; + m_state.context.fpu.__fpu_xmm15.__xmm_reg[i] = 'F'; + } + for (i=0; i<sizeof(m_state.context.fpu.__fpu_rsrv4); ++i) + m_state.context.fpu.__fpu_rsrv4[i] = INT8_MIN; + m_state.context.fpu.__fpu_reserved1 = -1; + m_state.SetError(e_regSetFPU, Read, 0); +#else + mach_msg_type_number_t count = x86_FLOAT_STATE64_COUNT; + m_state.SetError(e_regSetFPU, Read, ::thread_get_state(m_thread->ThreadID(), x86_FLOAT_STATE64, (thread_state_t)&m_state.context.fpu, &count)); +#endif + } + return m_state.GetError(e_regSetFPU, Read); +} + +kern_return_t +DNBArchImplX86_64::GetEXCState(bool force) +{ + if (force || m_state.GetError(e_regSetEXC, Read)) + { + mach_msg_type_number_t count = X86_EXCEPTION_STATE64_COUNT; + m_state.SetError(e_regSetEXC, Read, ::thread_get_state(m_thread->ThreadID(), x86_EXCEPTION_STATE64, (thread_state_t)&m_state.context.exc, &count)); + } + return m_state.GetError(e_regSetEXC, Read); +} + +kern_return_t +DNBArchImplX86_64::SetGPRState() +{ + m_state.SetError(e_regSetGPR, Write, ::thread_set_state(m_thread->ThreadID(), x86_THREAD_STATE64, (thread_state_t)&m_state.context.gpr, x86_THREAD_STATE64_COUNT)); + DNBLogThreadedIf (LOG_THREAD, "::thread_set_state (0x%4.4x, %u, &gpr, %u) => 0x%8.8x" + "\n\trax = %16.16llx rbx = %16.16llx rcx = %16.16llx rdx = %16.16llx" + "\n\trdi = %16.16llx rsi = %16.16llx rbp = %16.16llx rsp = %16.16llx" + "\n\t r8 = %16.16llx r9 = %16.16llx r10 = %16.16llx r11 = %16.16llx" + "\n\tr12 = %16.16llx r13 = %16.16llx r14 = %16.16llx r15 = %16.16llx" + "\n\trip = %16.16llx" + "\n\tflg = %16.16llx cs = %16.16llx fs = %16.16llx gs = %16.16llx", + m_thread->ThreadID(), x86_THREAD_STATE64, x86_THREAD_STATE64_COUNT, + m_state.GetError(e_regSetGPR, Write), + m_state.context.gpr.__rax,m_state.context.gpr.__rbx,m_state.context.gpr.__rcx, + m_state.context.gpr.__rdx,m_state.context.gpr.__rdi,m_state.context.gpr.__rsi, + m_state.context.gpr.__rbp,m_state.context.gpr.__rsp,m_state.context.gpr.__r8, + m_state.context.gpr.__r9, m_state.context.gpr.__r10,m_state.context.gpr.__r11, + m_state.context.gpr.__r12,m_state.context.gpr.__r13,m_state.context.gpr.__r14, + m_state.context.gpr.__r15,m_state.context.gpr.__rip,m_state.context.gpr.__rflags, + m_state.context.gpr.__cs, m_state.context.gpr.__fs, m_state.context.gpr.__gs); + return m_state.GetError(e_regSetGPR, Write); +} + +kern_return_t +DNBArchImplX86_64::SetFPUState() +{ + m_state.SetError(e_regSetFPU, Write, ::thread_set_state(m_thread->ThreadID(), x86_FLOAT_STATE64, (thread_state_t)&m_state.context.fpu, x86_FLOAT_STATE64_COUNT)); + return m_state.GetError(e_regSetFPU, Write); +} + +kern_return_t +DNBArchImplX86_64::SetEXCState() +{ + m_state.SetError(e_regSetEXC, Write, ::thread_set_state(m_thread->ThreadID(), x86_EXCEPTION_STATE64, (thread_state_t)&m_state.context.exc, X86_EXCEPTION_STATE64_COUNT)); + return m_state.GetError(e_regSetEXC, Write); +} + +void +DNBArchImplX86_64::ThreadWillResume() +{ + // Do we need to step this thread? If so, let the mach thread tell us so. + if (m_thread->IsStepping()) + { + // This is the primary thread, let the arch do anything it needs + EnableHardwareSingleStep(true) == KERN_SUCCESS; + } +} + +bool +DNBArchImplX86_64::ThreadDidStop() +{ + bool success = true; + + m_state.InvalidateAllRegisterStates(); + + // Are we stepping a single instruction? + if (GetGPRState(true) == KERN_SUCCESS) + { + // We are single stepping, was this the primary thread? + if (m_thread->IsStepping()) + { + // This was the primary thread, we need to clear the trace + // bit if so. + success = EnableHardwareSingleStep(false) == KERN_SUCCESS; + } + else + { + // The MachThread will automatically restore the suspend count + // in ThreadDidStop(), so we don't need to do anything here if + // we weren't the primary thread the last time + } + } + return success; +} + +bool +DNBArchImplX86_64::NotifyException(MachException::Data& exc) +{ + switch (exc.exc_type) + { + case EXC_BAD_ACCESS: + break; + case EXC_BAD_INSTRUCTION: + break; + case EXC_ARITHMETIC: + break; + case EXC_EMULATION: + break; + case EXC_SOFTWARE: + break; + case EXC_BREAKPOINT: + if (exc.exc_data.size() >= 2 && exc.exc_data[0] == 2) + { + nub_addr_t pc = GetPC(INVALID_NUB_ADDRESS); + if (pc != INVALID_NUB_ADDRESS && pc > 0) + { + pc -= 1; + // Check for a breakpoint at one byte prior to the current PC value + // since the PC will be just past the trap. + + nub_break_t breakID = m_thread->Process()->Breakpoints().FindIDByAddress(pc); + if (NUB_BREAK_ID_IS_VALID(breakID)) + { + // Backup the PC for i386 since the trap was taken and the PC + // is at the address following the single byte trap instruction. + if (m_state.context.gpr.__rip > 0) + { + m_state.context.gpr.__rip = pc; + // Write the new PC back out + SetGPRState (); + } + + m_thread->SetCurrentBreakpoint(breakID); + } + return true; + } + } + break; + case EXC_SYSCALL: + break; + case EXC_MACH_SYSCALL: + break; + case EXC_RPC_ALERT: + break; + } + return false; +} + + +// Set the single step bit in the processor status register. +kern_return_t +DNBArchImplX86_64::EnableHardwareSingleStep (bool enable) +{ + if (GetGPRState(false) == KERN_SUCCESS) + { + const uint32_t trace_bit = 0x100u; + if (enable) + m_state.context.gpr.__rflags |= trace_bit; + else + m_state.context.gpr.__rflags &= ~trace_bit; + return SetGPRState(); + } + return m_state.GetError(e_regSetGPR, Read); +} + + +//---------------------------------------------------------------------- +// Register information defintions +//---------------------------------------------------------------------- + +enum +{ + gpr_rax = 0, + gpr_rbx, + gpr_rcx, + gpr_rdx, + gpr_rdi, + gpr_rsi, + gpr_rbp, + gpr_rsp, + gpr_r8, + gpr_r9, + gpr_r10, + gpr_r11, + gpr_r12, + gpr_r13, + gpr_r14, + gpr_r15, + gpr_rip, + gpr_rflags, + gpr_cs, + gpr_fs, + gpr_gs, + k_num_gpr_regs +}; + +enum { + fpu_fcw, + fpu_fsw, + fpu_ftw, + fpu_fop, + fpu_ip, + fpu_cs, + fpu_dp, + fpu_ds, + fpu_mxcsr, + fpu_mxcsrmask, + fpu_stmm0, + fpu_stmm1, + fpu_stmm2, + fpu_stmm3, + fpu_stmm4, + fpu_stmm5, + fpu_stmm6, + fpu_stmm7, + fpu_xmm0, + fpu_xmm1, + fpu_xmm2, + fpu_xmm3, + fpu_xmm4, + fpu_xmm5, + fpu_xmm6, + fpu_xmm7, + fpu_xmm8, + fpu_xmm9, + fpu_xmm10, + fpu_xmm11, + fpu_xmm12, + fpu_xmm13, + fpu_xmm14, + fpu_xmm15, + k_num_fpu_regs, + + // Aliases + fpu_fctrl = fpu_fcw, + fpu_fstat = fpu_fsw, + fpu_ftag = fpu_ftw, + fpu_fiseg = fpu_cs, + fpu_fioff = fpu_ip, + fpu_foseg = fpu_ds, + fpu_fooff = fpu_dp +}; + +enum { + exc_trapno, + exc_err, + exc_faultvaddr, + k_num_exc_regs, +}; + + +enum gcc_dwarf_regnums +{ + gcc_dwarf_rax = 0, + gcc_dwarf_rdx, + gcc_dwarf_rcx, + gcc_dwarf_rbx, + gcc_dwarf_rsi, + gcc_dwarf_rdi, + gcc_dwarf_rbp, + gcc_dwarf_rsp, + gcc_dwarf_r8, + gcc_dwarf_r9, + gcc_dwarf_r10, + gcc_dwarf_r11, + gcc_dwarf_r12, + gcc_dwarf_r13, + gcc_dwarf_r14, + gcc_dwarf_r15, + gcc_dwarf_rip, + gcc_dwarf_xmm0, + gcc_dwarf_xmm1, + gcc_dwarf_xmm2, + gcc_dwarf_xmm3, + gcc_dwarf_xmm4, + gcc_dwarf_xmm5, + gcc_dwarf_xmm6, + gcc_dwarf_xmm7, + gcc_dwarf_xmm8, + gcc_dwarf_xmm9, + gcc_dwarf_xmm10, + gcc_dwarf_xmm11, + gcc_dwarf_xmm12, + gcc_dwarf_xmm13, + gcc_dwarf_xmm14, + gcc_dwarf_xmm15, + gcc_dwarf_stmm0, + gcc_dwarf_stmm1, + gcc_dwarf_stmm2, + gcc_dwarf_stmm3, + gcc_dwarf_stmm4, + gcc_dwarf_stmm5, + gcc_dwarf_stmm6, + gcc_dwarf_stmm7, + +}; + +enum gdb_regnums +{ + gdb_rax = 0, + gdb_rbx = 1, + gdb_rcx = 2, + gdb_rdx = 3, + gdb_rsi = 4, + gdb_rdi = 5, + gdb_rbp = 6, + gdb_rsp = 7, + gdb_r8 = 8, + gdb_r9 = 9, + gdb_r10 = 10, + gdb_r11 = 11, + gdb_r12 = 12, + gdb_r13 = 13, + gdb_r14 = 14, + gdb_r15 = 15, + gdb_rip = 16, + gdb_rflags = 17, + gdb_cs = 18, + gdb_ss = 19, + gdb_ds = 20, + gdb_es = 21, + gdb_fs = 22, + gdb_gs = 23, + gdb_stmm0 = 24, + gdb_stmm1 = 25, + gdb_stmm2 = 26, + gdb_stmm3 = 27, + gdb_stmm4 = 28, + gdb_stmm5 = 29, + gdb_stmm6 = 30, + gdb_stmm7 = 31, + gdb_fctrl = 32, gdb_fcw = gdb_fctrl, + gdb_fstat = 33, gdb_fsw = gdb_fstat, + gdb_ftag = 34, gdb_ftw = gdb_ftag, + gdb_fiseg = 35, gdb_fpu_cs = gdb_fiseg, + gdb_fioff = 36, gdb_ip = gdb_fioff, + gdb_foseg = 37, gdb_fpu_ds = gdb_foseg, + gdb_fooff = 38, gdb_dp = gdb_fooff, + gdb_fop = 39, + gdb_xmm0 = 40, + gdb_xmm1 = 41, + gdb_xmm2 = 42, + gdb_xmm3 = 43, + gdb_xmm4 = 44, + gdb_xmm5 = 45, + gdb_xmm6 = 46, + gdb_xmm7 = 47, + gdb_xmm8 = 48, + gdb_xmm9 = 49, + gdb_xmm10 = 50, + gdb_xmm11 = 51, + gdb_xmm12 = 52, + gdb_xmm13 = 53, + gdb_xmm14 = 54, + gdb_xmm15 = 55, + gdb_mxcsr = 56, +}; + +#define GPR_OFFSET(reg) (offsetof (DNBArchImplX86_64::GPR, __##reg)) +#define FPU_OFFSET(reg) (offsetof (DNBArchImplX86_64::FPU, __fpu_##reg) + offsetof (DNBArchImplX86_64::Context, fpu)) +#define EXC_OFFSET(reg) (offsetof (DNBArchImplX86_64::EXC, __##reg) + offsetof (DNBArchImplX86_64::Context, exc)) + +#define GPR_SIZE(reg) (sizeof(((DNBArchImplX86_64::GPR *)NULL)->__##reg)) +#define FPU_SIZE_UINT(reg) (sizeof(((DNBArchImplX86_64::FPU *)NULL)->__fpu_##reg)) +#define FPU_SIZE_MMST(reg) (sizeof(((DNBArchImplX86_64::FPU *)NULL)->__fpu_##reg.__mmst_reg)) +#define FPU_SIZE_XMM(reg) (sizeof(((DNBArchImplX86_64::FPU *)NULL)->__fpu_##reg.__xmm_reg)) +#define EXC_SIZE(reg) (sizeof(((DNBArchImplX86_64::EXC *)NULL)->__##reg)) + +// These macros will auto define the register name, alt name, register size, +// register offset, encoding, format and native register. This ensures that +// the register state structures are defined correctly and have the correct +// sizes and offsets. +#define DEFINE_GPR(reg) { e_regSetGPR, gpr_##reg, #reg, NULL, Uint, Hex, GPR_SIZE(reg), GPR_OFFSET(reg), gcc_dwarf_##reg, gcc_dwarf_##reg, INVALID_NUB_REGNUM, gdb_##reg } +#define DEFINE_GPR_ALT(reg, alt, gen) { e_regSetGPR, gpr_##reg, #reg, alt, Uint, Hex, GPR_SIZE(reg), GPR_OFFSET(reg), gcc_dwarf_##reg, gcc_dwarf_##reg, gen, gdb_##reg } +#define DEFINE_GPR_ALT2(reg, alt) { e_regSetGPR, gpr_##reg, #reg, alt, Uint, Hex, GPR_SIZE(reg), GPR_OFFSET(reg), INVALID_NUB_REGNUM, INVALID_NUB_REGNUM, INVALID_NUB_REGNUM, gdb_##reg } + +// General purpose registers for 64 bit +const DNBRegisterInfo +DNBArchImplX86_64::g_gpr_registers[] = +{ + DEFINE_GPR (rax), + DEFINE_GPR (rbx), + DEFINE_GPR (rcx), + DEFINE_GPR (rdx), + DEFINE_GPR (rdi), + DEFINE_GPR (rsi), + DEFINE_GPR_ALT (rbp, "fp", GENERIC_REGNUM_FP), + DEFINE_GPR_ALT (rsp, "sp", GENERIC_REGNUM_SP), + DEFINE_GPR (r8), + DEFINE_GPR (r9), + DEFINE_GPR (r10), + DEFINE_GPR (r11), + DEFINE_GPR (r12), + DEFINE_GPR (r13), + DEFINE_GPR (r14), + DEFINE_GPR (r15), + DEFINE_GPR_ALT (rip, "pc", GENERIC_REGNUM_PC), + DEFINE_GPR_ALT2 (rflags, "flags"), + DEFINE_GPR_ALT2 (cs, NULL), + DEFINE_GPR_ALT2 (fs, NULL), + DEFINE_GPR_ALT2 (gs, NULL), +}; + +// Floating point registers 64 bit +const DNBRegisterInfo +DNBArchImplX86_64::g_fpu_registers[] = +{ + { e_regSetFPU, fpu_fcw , "fctrl" , NULL, Uint, Hex, FPU_SIZE_UINT(fcw) , FPU_OFFSET(fcw) , -1, -1, -1, -1 }, + { e_regSetFPU, fpu_fsw , "fstat" , NULL, Uint, Hex, FPU_SIZE_UINT(fsw) , FPU_OFFSET(fsw) , -1, -1, -1, -1 }, + { e_regSetFPU, fpu_ftw , "ftag" , NULL, Uint, Hex, FPU_SIZE_UINT(ftw) , FPU_OFFSET(ftw) , -1, -1, -1, -1 }, + { e_regSetFPU, fpu_fop , "fop" , NULL, Uint, Hex, FPU_SIZE_UINT(fop) , FPU_OFFSET(fop) , -1, -1, -1, -1 }, + { e_regSetFPU, fpu_ip , "fioff" , NULL, Uint, Hex, FPU_SIZE_UINT(ip) , FPU_OFFSET(ip) , -1, -1, -1, -1 }, + { e_regSetFPU, fpu_cs , "fiseg" , NULL, Uint, Hex, FPU_SIZE_UINT(cs) , FPU_OFFSET(cs) , -1, -1, -1, -1 }, + { e_regSetFPU, fpu_dp , "fooff" , NULL, Uint, Hex, FPU_SIZE_UINT(dp) , FPU_OFFSET(dp) , -1, -1, -1, -1 }, + { e_regSetFPU, fpu_ds , "foseg" , NULL, Uint, Hex, FPU_SIZE_UINT(ds) , FPU_OFFSET(ds) , -1, -1, -1, -1 }, + { e_regSetFPU, fpu_mxcsr , "mxcsr" , NULL, Uint, Hex, FPU_SIZE_UINT(mxcsr) , FPU_OFFSET(mxcsr) , -1, -1, -1, -1 }, + { e_regSetFPU, fpu_mxcsrmask, "mxcsrmask" , NULL, Uint, Hex, FPU_SIZE_UINT(mxcsrmask) , FPU_OFFSET(mxcsrmask) , -1, -1, -1, -1 }, + + { e_regSetFPU, fpu_stmm0, "stmm0", NULL, Vector, VectorOfUInt8, FPU_SIZE_MMST(stmm0), FPU_OFFSET(stmm0), gcc_dwarf_stmm0, gcc_dwarf_stmm0, -1, gdb_stmm0 }, + { e_regSetFPU, fpu_stmm1, "stmm1", NULL, Vector, VectorOfUInt8, FPU_SIZE_MMST(stmm1), FPU_OFFSET(stmm1), gcc_dwarf_stmm1, gcc_dwarf_stmm1, -1, gdb_stmm1 }, + { e_regSetFPU, fpu_stmm2, "stmm2", NULL, Vector, VectorOfUInt8, FPU_SIZE_MMST(stmm2), FPU_OFFSET(stmm2), gcc_dwarf_stmm2, gcc_dwarf_stmm2, -1, gdb_stmm2 }, + { e_regSetFPU, fpu_stmm3, "stmm3", NULL, Vector, VectorOfUInt8, FPU_SIZE_MMST(stmm3), FPU_OFFSET(stmm3), gcc_dwarf_stmm3, gcc_dwarf_stmm3, -1, gdb_stmm3 }, + { e_regSetFPU, fpu_stmm4, "stmm4", NULL, Vector, VectorOfUInt8, FPU_SIZE_MMST(stmm4), FPU_OFFSET(stmm4), gcc_dwarf_stmm4, gcc_dwarf_stmm4, -1, gdb_stmm4 }, + { e_regSetFPU, fpu_stmm5, "stmm5", NULL, Vector, VectorOfUInt8, FPU_SIZE_MMST(stmm5), FPU_OFFSET(stmm5), gcc_dwarf_stmm5, gcc_dwarf_stmm5, -1, gdb_stmm5 }, + { e_regSetFPU, fpu_stmm6, "stmm6", NULL, Vector, VectorOfUInt8, FPU_SIZE_MMST(stmm6), FPU_OFFSET(stmm6), gcc_dwarf_stmm6, gcc_dwarf_stmm6, -1, gdb_stmm6 }, + { e_regSetFPU, fpu_stmm7, "stmm7", NULL, Vector, VectorOfUInt8, FPU_SIZE_MMST(stmm7), FPU_OFFSET(stmm7), gcc_dwarf_stmm7, gcc_dwarf_stmm7, -1, gdb_stmm7 }, + + { e_regSetFPU, fpu_xmm0 , "xmm0" , NULL, Vector, VectorOfUInt8, FPU_SIZE_XMM(xmm0) , FPU_OFFSET(xmm0) , gcc_dwarf_xmm0 , gcc_dwarf_xmm0 , -1, gdb_xmm0 }, + { e_regSetFPU, fpu_xmm1 , "xmm1" , NULL, Vector, VectorOfUInt8, FPU_SIZE_XMM(xmm1) , FPU_OFFSET(xmm1) , gcc_dwarf_xmm1 , gcc_dwarf_xmm1 , -1, gdb_xmm1 }, + { e_regSetFPU, fpu_xmm2 , "xmm2" , NULL, Vector, VectorOfUInt8, FPU_SIZE_XMM(xmm2) , FPU_OFFSET(xmm2) , gcc_dwarf_xmm2 , gcc_dwarf_xmm2 , -1, gdb_xmm2 }, + { e_regSetFPU, fpu_xmm3 , "xmm3" , NULL, Vector, VectorOfUInt8, FPU_SIZE_XMM(xmm3) , FPU_OFFSET(xmm3) , gcc_dwarf_xmm3 , gcc_dwarf_xmm3 , -1, gdb_xmm3 }, + { e_regSetFPU, fpu_xmm4 , "xmm4" , NULL, Vector, VectorOfUInt8, FPU_SIZE_XMM(xmm4) , FPU_OFFSET(xmm4) , gcc_dwarf_xmm4 , gcc_dwarf_xmm4 , -1, gdb_xmm4 }, + { e_regSetFPU, fpu_xmm5 , "xmm5" , NULL, Vector, VectorOfUInt8, FPU_SIZE_XMM(xmm5) , FPU_OFFSET(xmm5) , gcc_dwarf_xmm5 , gcc_dwarf_xmm5 , -1, gdb_xmm5 }, + { e_regSetFPU, fpu_xmm6 , "xmm6" , NULL, Vector, VectorOfUInt8, FPU_SIZE_XMM(xmm6) , FPU_OFFSET(xmm6) , gcc_dwarf_xmm6 , gcc_dwarf_xmm6 , -1, gdb_xmm6 }, + { e_regSetFPU, fpu_xmm7 , "xmm7" , NULL, Vector, VectorOfUInt8, FPU_SIZE_XMM(xmm7) , FPU_OFFSET(xmm7) , gcc_dwarf_xmm7 , gcc_dwarf_xmm7 , -1, gdb_xmm7 }, + { e_regSetFPU, fpu_xmm8 , "xmm8" , NULL, Vector, VectorOfUInt8, FPU_SIZE_XMM(xmm8) , FPU_OFFSET(xmm8) , gcc_dwarf_xmm8 , gcc_dwarf_xmm8 , -1, gdb_xmm8 }, + { e_regSetFPU, fpu_xmm9 , "xmm9" , NULL, Vector, VectorOfUInt8, FPU_SIZE_XMM(xmm9) , FPU_OFFSET(xmm9) , gcc_dwarf_xmm9 , gcc_dwarf_xmm9 , -1, gdb_xmm9 }, + { e_regSetFPU, fpu_xmm10, "xmm10" , NULL, Vector, VectorOfUInt8, FPU_SIZE_XMM(xmm10) , FPU_OFFSET(xmm10), gcc_dwarf_xmm10, gcc_dwarf_xmm10, -1, gdb_xmm10 }, + { e_regSetFPU, fpu_xmm11, "xmm11" , NULL, Vector, VectorOfUInt8, FPU_SIZE_XMM(xmm11) , FPU_OFFSET(xmm11), gcc_dwarf_xmm11, gcc_dwarf_xmm11, -1, gdb_xmm11 }, + { e_regSetFPU, fpu_xmm12, "xmm12" , NULL, Vector, VectorOfUInt8, FPU_SIZE_XMM(xmm12) , FPU_OFFSET(xmm12), gcc_dwarf_xmm12, gcc_dwarf_xmm12, -1, gdb_xmm12 }, + { e_regSetFPU, fpu_xmm13, "xmm13" , NULL, Vector, VectorOfUInt8, FPU_SIZE_XMM(xmm13) , FPU_OFFSET(xmm13), gcc_dwarf_xmm13, gcc_dwarf_xmm13, -1, gdb_xmm13 }, + { e_regSetFPU, fpu_xmm14, "xmm14" , NULL, Vector, VectorOfUInt8, FPU_SIZE_XMM(xmm14) , FPU_OFFSET(xmm14), gcc_dwarf_xmm14, gcc_dwarf_xmm14, -1, gdb_xmm14 }, + { e_regSetFPU, fpu_xmm15, "xmm15" , NULL, Vector, VectorOfUInt8, FPU_SIZE_XMM(xmm15) , FPU_OFFSET(xmm15), gcc_dwarf_xmm15, gcc_dwarf_xmm15, -1, gdb_xmm15 }, +}; + +// Exception registers + +const DNBRegisterInfo +DNBArchImplX86_64::g_exc_registers[] = +{ + { e_regSetEXC, exc_trapno, "trapno" , NULL, Uint, Hex, EXC_SIZE (trapno) , EXC_OFFSET (trapno) , -1, -1, -1, -1 }, + { e_regSetEXC, exc_err, "err" , NULL, Uint, Hex, EXC_SIZE (err) , EXC_OFFSET (err) , -1, -1, -1, -1 }, + { e_regSetEXC, exc_faultvaddr, "faultvaddr", NULL, Uint, Hex, EXC_SIZE (faultvaddr), EXC_OFFSET (faultvaddr) , -1, -1, -1, -1 } +}; + +// Number of registers in each register set +const size_t DNBArchImplX86_64::k_num_gpr_registers = sizeof(g_gpr_registers)/sizeof(DNBRegisterInfo); +const size_t DNBArchImplX86_64::k_num_fpu_registers = sizeof(g_fpu_registers)/sizeof(DNBRegisterInfo); +const size_t DNBArchImplX86_64::k_num_exc_registers = sizeof(g_exc_registers)/sizeof(DNBRegisterInfo); +const size_t DNBArchImplX86_64::k_num_all_registers = k_num_gpr_registers + k_num_fpu_registers + k_num_exc_registers; + +//---------------------------------------------------------------------- +// Register set definitions. The first definitions at register set index +// of zero is for all registers, followed by other registers sets. The +// register information for the all register set need not be filled in. +//---------------------------------------------------------------------- +const DNBRegisterSetInfo +DNBArchImplX86_64::g_reg_sets[] = +{ + { "x86_64 Registers", NULL, k_num_all_registers }, + { "General Purpose Registers", g_gpr_registers, k_num_gpr_registers }, + { "Floating Point Registers", g_fpu_registers, k_num_fpu_registers }, + { "Exception State Registers", g_exc_registers, k_num_exc_registers } +}; +// Total number of register sets for this architecture +const size_t DNBArchImplX86_64::k_num_register_sets = sizeof(g_reg_sets)/sizeof(DNBRegisterSetInfo); + + +const DNBRegisterSetInfo * +DNBArchImplX86_64::GetRegisterSetInfo (nub_size_t *num_reg_sets) +{ + *num_reg_sets = k_num_register_sets; + return g_reg_sets; +} + +bool +DNBArchImplX86_64::GetRegisterValue(int set, int reg, DNBRegisterValue *value) +{ + if (set == REGISTER_SET_GENERIC) + { + switch (reg) + { + case GENERIC_REGNUM_PC: // Program Counter + set = e_regSetGPR; + reg = gpr_rip; + break; + + case GENERIC_REGNUM_SP: // Stack Pointer + set = e_regSetGPR; + reg = gpr_rsp; + break; + + case GENERIC_REGNUM_FP: // Frame Pointer + set = e_regSetGPR; + reg = gpr_rbp; + break; + + case GENERIC_REGNUM_FLAGS: // Processor flags register + set = e_regSetGPR; + reg = gpr_rflags; + break; + + case GENERIC_REGNUM_RA: // Return Address + default: + return false; + } + } + + if (GetRegisterState(set, false) != KERN_SUCCESS) + return false; + + const DNBRegisterInfo *regInfo = m_thread->GetRegisterInfo(set, reg); + if (regInfo) + { + value->info = *regInfo; + switch (set) + { + case e_regSetGPR: + if (reg < k_num_gpr_registers) + { + value->value.uint64 = ((uint64_t*)(&m_state.context.gpr))[reg]; + return true; + } + break; + + case e_regSetFPU: + switch (reg) + { + case fpu_fcw: value->value.uint16 = *((uint16_t *)(&m_state.context.fpu.__fpu_fcw)); return true; + case fpu_fsw: value->value.uint16 = *((uint16_t *)(&m_state.context.fpu.__fpu_fsw)); return true; + case fpu_ftw: value->value.uint8 = m_state.context.fpu.__fpu_ftw; return true; + case fpu_fop: value->value.uint16 = m_state.context.fpu.__fpu_fop; return true; + case fpu_ip: value->value.uint32 = m_state.context.fpu.__fpu_ip; return true; + case fpu_cs: value->value.uint16 = m_state.context.fpu.__fpu_cs; return true; + case fpu_dp: value->value.uint32 = m_state.context.fpu.__fpu_dp; return true; + case fpu_ds: value->value.uint16 = m_state.context.fpu.__fpu_ds; return true; + case fpu_mxcsr: value->value.uint32 = m_state.context.fpu.__fpu_mxcsr; return true; + case fpu_mxcsrmask: value->value.uint32 = m_state.context.fpu.__fpu_mxcsrmask; return true; + + case fpu_stmm0: + case fpu_stmm1: + case fpu_stmm2: + case fpu_stmm3: + case fpu_stmm4: + case fpu_stmm5: + case fpu_stmm6: + case fpu_stmm7: + memcpy(&value->value.uint8, &m_state.context.fpu.__fpu_stmm0 + (reg - fpu_stmm0), 10); + return true; + + case fpu_xmm0: + case fpu_xmm1: + case fpu_xmm2: + case fpu_xmm3: + case fpu_xmm4: + case fpu_xmm5: + case fpu_xmm6: + case fpu_xmm7: + case fpu_xmm8: + case fpu_xmm9: + case fpu_xmm10: + case fpu_xmm11: + case fpu_xmm12: + case fpu_xmm13: + case fpu_xmm14: + case fpu_xmm15: + memcpy(&value->value.uint8, &m_state.context.fpu.__fpu_xmm0 + (reg - fpu_xmm0), 16); + return true; + } + break; + + case e_regSetEXC: + switch (reg) + { + case exc_trapno: value->value.uint32 = m_state.context.exc.__trapno; return true; + case exc_err: value->value.uint32 = m_state.context.exc.__err; return true; + case exc_faultvaddr:value->value.uint64 = m_state.context.exc.__faultvaddr; return true; + } + break; + } + } + return false; +} + + +bool +DNBArchImplX86_64::SetRegisterValue(int set, int reg, const DNBRegisterValue *value) +{ + if (set == REGISTER_SET_GENERIC) + { + switch (reg) + { + case GENERIC_REGNUM_PC: // Program Counter + set = e_regSetGPR; + reg = gpr_rip; + break; + + case GENERIC_REGNUM_SP: // Stack Pointer + set = e_regSetGPR; + reg = gpr_rsp; + break; + + case GENERIC_REGNUM_FP: // Frame Pointer + set = e_regSetGPR; + reg = gpr_rbp; + break; + + case GENERIC_REGNUM_FLAGS: // Processor flags register + set = e_regSetGPR; + reg = gpr_rflags; + break; + + case GENERIC_REGNUM_RA: // Return Address + default: + return false; + } + } + + if (GetRegisterState(set, false) != KERN_SUCCESS) + return false; + + bool success = false; + const DNBRegisterInfo *regInfo = m_thread->GetRegisterInfo(set, reg); + if (regInfo) + { + switch (set) + { + case e_regSetGPR: + if (reg < k_num_gpr_registers) + { + ((uint64_t*)(&m_state.context.gpr))[reg] = value->value.uint64; + success = true; + } + break; + + case e_regSetFPU: + switch (reg) + { + case fpu_fcw: *((uint16_t *)(&m_state.context.fpu.__fpu_fcw)) = value->value.uint16; success = true; break; + case fpu_fsw: *((uint16_t *)(&m_state.context.fpu.__fpu_fsw)) = value->value.uint16; success = true; break; + case fpu_ftw: m_state.context.fpu.__fpu_ftw = value->value.uint8; success = true; break; + case fpu_fop: m_state.context.fpu.__fpu_fop = value->value.uint16; success = true; break; + case fpu_ip: m_state.context.fpu.__fpu_ip = value->value.uint32; success = true; break; + case fpu_cs: m_state.context.fpu.__fpu_cs = value->value.uint16; success = true; break; + case fpu_dp: m_state.context.fpu.__fpu_dp = value->value.uint32; success = true; break; + case fpu_ds: m_state.context.fpu.__fpu_ds = value->value.uint16; success = true; break; + case fpu_mxcsr: m_state.context.fpu.__fpu_mxcsr = value->value.uint32; success = true; break; + case fpu_mxcsrmask: m_state.context.fpu.__fpu_mxcsrmask = value->value.uint32; success = true; break; + + case fpu_stmm0: + case fpu_stmm1: + case fpu_stmm2: + case fpu_stmm3: + case fpu_stmm4: + case fpu_stmm5: + case fpu_stmm6: + case fpu_stmm7: + memcpy (&m_state.context.fpu.__fpu_stmm0 + (reg - fpu_stmm0), &value->value.uint8, 10); + success = true; + break; + + case fpu_xmm0: + case fpu_xmm1: + case fpu_xmm2: + case fpu_xmm3: + case fpu_xmm4: + case fpu_xmm5: + case fpu_xmm6: + case fpu_xmm7: + case fpu_xmm8: + case fpu_xmm9: + case fpu_xmm10: + case fpu_xmm11: + case fpu_xmm12: + case fpu_xmm13: + case fpu_xmm14: + case fpu_xmm15: + memcpy (&m_state.context.fpu.__fpu_xmm0 + (reg - fpu_xmm0), &value->value.uint8, 16); + success = true; + break; + } + break; + + case e_regSetEXC: + switch (reg) + { + case exc_trapno: m_state.context.exc.__trapno = value->value.uint32; success = true; break; + case exc_err: m_state.context.exc.__err = value->value.uint32; success = true; break; + case exc_faultvaddr:m_state.context.exc.__faultvaddr = value->value.uint64; success = true; break; + } + break; + } + } + + if (success) + return SetRegisterState(set) == KERN_SUCCESS; + return false; +} + + +nub_size_t +DNBArchImplX86_64::GetRegisterContext (void *buf, nub_size_t buf_len) +{ + nub_size_t size = sizeof (m_state.context); + + if (buf && buf_len) + { + if (size > buf_len) + size = buf_len; + + bool force = false; + if (GetGPRState(force) | GetFPUState(force) | GetEXCState(force)) + return 0; + ::memcpy (buf, &m_state.context, size); + } + DNBLogThreadedIf (LOG_THREAD, "DNBArchImplX86_64::GetRegisterContext (buf = %p, len = %zu) => %zu", buf, buf_len, size); + // Return the size of the register context even if NULL was passed in + return size; +} + +nub_size_t +DNBArchImplX86_64::SetRegisterContext (const void *buf, nub_size_t buf_len) +{ + nub_size_t size = sizeof (m_state.context); + if (buf == NULL || buf_len == 0) + size = 0; + + if (size) + { + if (size > buf_len) + size = buf_len; + + ::memcpy (&m_state.context, buf, size); + SetGPRState(); + SetFPUState(); + SetEXCState(); + } + DNBLogThreadedIf (LOG_THREAD, "DNBArchImplX86_64::SetRegisterContext (buf = %p, len = %zu) => %zu", buf, buf_len, size); + return size; +} + + +kern_return_t +DNBArchImplX86_64::GetRegisterState(int set, bool force) +{ + switch (set) + { + case e_regSetALL: return GetGPRState(force) | GetFPUState(force) | GetEXCState(force); + case e_regSetGPR: return GetGPRState(force); + case e_regSetFPU: return GetFPUState(force); + case e_regSetEXC: return GetEXCState(force); + default: break; + } + return KERN_INVALID_ARGUMENT; +} + +kern_return_t +DNBArchImplX86_64::SetRegisterState(int set) +{ + // Make sure we have a valid context to set. + if (RegisterSetStateIsValid(set)) + { + switch (set) + { + case e_regSetALL: return SetGPRState() | SetFPUState() | SetEXCState(); + case e_regSetGPR: return SetGPRState(); + case e_regSetFPU: return SetFPUState(); + case e_regSetEXC: return SetEXCState(); + default: break; + } + } + return KERN_INVALID_ARGUMENT; +} + +bool +DNBArchImplX86_64::RegisterSetStateIsValid (int set) const +{ + return m_state.RegsAreValid(set); +} + + + +#endif // #if defined (__i386__) diff --git a/lldb/tools/debugserver/source/MacOSX/x86_64/DNBArchImplX86_64.h b/lldb/tools/debugserver/source/MacOSX/x86_64/DNBArchImplX86_64.h new file mode 100644 index 00000000000..f445d473892 --- /dev/null +++ b/lldb/tools/debugserver/source/MacOSX/x86_64/DNBArchImplX86_64.h @@ -0,0 +1,199 @@ +//===-- DNBArchImplX86_64.h -------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/25/07. +// +//===----------------------------------------------------------------------===// + +#ifndef __DNBArchImplX86_64_h__ +#define __DNBArchImplX86_64_h__ + +//#if defined (__i386__) +#if defined(__x86_64__) +#include "DNBArch.h" +#include <mach/mach_types.h> +#include <mach/thread_status.h> + + +class MachThread; + +class DNBArchImplX86_64 : public DNBArchProtocol +{ +public: + DNBArchImplX86_64(MachThread *thread) : + m_thread(thread), + m_state() + { + } + virtual ~DNBArchImplX86_64() + { + } + + static const DNBRegisterSetInfo * + GetRegisterSetInfo(nub_size_t *num_reg_sets); + + virtual bool GetRegisterValue(int set, int reg, DNBRegisterValue *reg); + virtual bool SetRegisterValue(int set, int reg, const DNBRegisterValue *reg); + virtual nub_size_t GetRegisterContext (void *buf, nub_size_t buf_len); + virtual nub_size_t SetRegisterContext (const void *buf, nub_size_t buf_len); + + virtual kern_return_t GetRegisterState (int set, bool force); + virtual kern_return_t SetRegisterState (int set); + virtual bool RegisterSetStateIsValid (int set) const; + + virtual uint64_t GetPC(uint64_t failValue); // Get program counter + virtual kern_return_t SetPC(uint64_t value); + virtual uint64_t GetSP(uint64_t failValue); // Get stack pointer + virtual void ThreadWillResume(); + virtual bool ThreadDidStop(); + virtual bool NotifyException(MachException::Data& exc); + + static const uint8_t * const SoftwareBreakpointOpcode (nub_size_t byte_size); + static uint32_t GetCPUType(); + +protected: + kern_return_t EnableHardwareSingleStep (bool enable); + + typedef x86_thread_state64_t GPR; + typedef x86_float_state64_t FPU; + typedef x86_exception_state64_t EXC; + + static const DNBRegisterInfo g_gpr_registers[]; + static const DNBRegisterInfo g_fpu_registers[]; + static const DNBRegisterInfo g_exc_registers[]; + static const DNBRegisterSetInfo g_reg_sets[]; + static const size_t k_num_gpr_registers; + static const size_t k_num_fpu_registers; + static const size_t k_num_exc_registers; + static const size_t k_num_all_registers; + static const size_t k_num_register_sets; + + typedef enum RegisterSetTag + { + e_regSetALL = REGISTER_SET_ALL, + e_regSetGPR, + e_regSetFPU, + e_regSetEXC, + kNumRegisterSets + } RegisterSet; + + + enum + { + Read = 0, + Write = 1, + kNumErrors = 2 + }; + + struct Context + { + GPR gpr; + FPU fpu; + EXC exc; + }; + + struct State + { + Context context; + kern_return_t gpr_errs[2]; // Read/Write errors + kern_return_t fpu_errs[2]; // Read/Write errors + kern_return_t exc_errs[2]; // Read/Write errors + + State() + { + uint32_t i; + for (i=0; i<kNumErrors; i++) + { + gpr_errs[i] = -1; + fpu_errs[i] = -1; + exc_errs[i] = -1; + } + } + + void + InvalidateAllRegisterStates() + { + SetError (e_regSetALL, Read, -1); + } + + kern_return_t + GetError (int flavor, uint32_t err_idx) const + { + if (err_idx < kNumErrors) + { + switch (flavor) + { + // When getting all errors, just OR all values together to see if + // we got any kind of error. + case e_regSetALL: return gpr_errs[err_idx] | + fpu_errs[err_idx] | + exc_errs[err_idx]; + case e_regSetGPR: return gpr_errs[err_idx]; + case e_regSetFPU: return fpu_errs[err_idx]; + case e_regSetEXC: return exc_errs[err_idx]; + default: break; + } + } + return -1; + } + + bool + SetError (int flavor, uint32_t err_idx, kern_return_t err) + { + if (err_idx < kNumErrors) + { + switch (flavor) + { + case e_regSetALL: + gpr_errs[err_idx] = + fpu_errs[err_idx] = + exc_errs[err_idx] = err; + return true; + + case e_regSetGPR: + gpr_errs[err_idx] = err; + return true; + + case e_regSetFPU: + fpu_errs[err_idx] = err; + return true; + + case e_regSetEXC: + exc_errs[err_idx] = err; + return true; + + default: break; + } + } + return false; + } + + bool + RegsAreValid (int flavor) const + { + return GetError(flavor, Read) == KERN_SUCCESS; + } + }; + + kern_return_t GetGPRState (bool force); + kern_return_t GetFPUState (bool force); + kern_return_t GetEXCState (bool force); + + kern_return_t SetGPRState (); + kern_return_t SetFPUState (); + kern_return_t SetEXCState (); + + MachThread *m_thread; + State m_state; +}; + +typedef DNBArchImplX86_64 DNBArch; + +#endif // #if defined (__x86_64__) +#endif // #ifndef __DNBArchImplX86_64_h__ diff --git a/lldb/tools/debugserver/source/PThreadCondition.h b/lldb/tools/debugserver/source/PThreadCondition.h new file mode 100644 index 00000000000..787cc7941d5 --- /dev/null +++ b/lldb/tools/debugserver/source/PThreadCondition.h @@ -0,0 +1,53 @@ +//===-- PThreadCondition.h --------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/16/07. +// +//===----------------------------------------------------------------------===// + +#ifndef __PThreadCondition_h__ +#define __PThreadCondition_h__ + +#include <pthread.h> + +class PThreadCondition +{ +public: + + PThreadCondition() + { + ::pthread_cond_init (&m_condition, NULL); + } + + ~PThreadCondition() + { + ::pthread_cond_destroy (&m_condition); + } + + pthread_cond_t *Condition() + { + return &m_condition; + } + + int Broadcast() + { + return ::pthread_cond_broadcast (&m_condition); + } + + int Signal() + { + return ::pthread_cond_signal (&m_condition); + } + +protected: + pthread_cond_t m_condition; +}; + +#endif + diff --git a/lldb/tools/debugserver/source/PThreadEvent.cpp b/lldb/tools/debugserver/source/PThreadEvent.cpp new file mode 100644 index 00000000000..b087bfc7d48 --- /dev/null +++ b/lldb/tools/debugserver/source/PThreadEvent.cpp @@ -0,0 +1,227 @@ +//===-- PThreadEvent.cpp ----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/16/07. +// +//===----------------------------------------------------------------------===// + +#include "PThreadEvent.h" +#include "errno.h" +#include "DNBLog.h" + +PThreadEvent::PThreadEvent(uint32_t bits, uint32_t validBits) : + m_mutex(), + m_set_condition(), + m_reset_condition(), + m_bits(bits), + m_validBits(validBits), + m_reset_ack_mask(0) +{ + // DNBLogThreadedIf(LOG_EVENTS, "%p PThreadEvent::%s (0x%8.8x, 0x%8.8x)", this, __FUNCTION__, bits, validBits); +} + +PThreadEvent::~PThreadEvent() +{ + // DNBLogThreadedIf(LOG_EVENTS, "%p %s", this, __PRETTY_FUNCTION__); +} + + +uint32_t +PThreadEvent::NewEventBit() +{ + // DNBLogThreadedIf(LOG_EVENTS, "%p %s", this, __PRETTY_FUNCTION__); + PTHREAD_MUTEX_LOCKER (locker, m_mutex); + uint32_t mask = 1; + while (mask & m_validBits) + mask <<= 1; + m_validBits |= mask; + return mask; +} + +void +PThreadEvent::FreeEventBits(const uint32_t mask) +{ + // DNBLogThreadedIf(LOG_EVENTS, "%p PThreadEvent::%s (0x%8.8x)", this, __FUNCTION__, mask); + if (mask) + { + PTHREAD_MUTEX_LOCKER (locker, m_mutex); + m_bits &= ~mask; + m_validBits &= ~mask; + } +} + + +uint32_t +PThreadEvent::GetEventBits() const +{ + // DNBLogThreadedIf(LOG_EVENTS, "%p %s", this, __PRETTY_FUNCTION__); + PTHREAD_MUTEX_LOCKER (locker, m_mutex); + uint32_t bits = m_bits; + return bits; +} + +// Replace the event bits with a new bitmask value +void +PThreadEvent::ReplaceEventBits(const uint32_t bits) +{ + // DNBLogThreadedIf(LOG_EVENTS, "%p PThreadEvent::%s (0x%8.8x)", this, __FUNCTION__, bits); + PTHREAD_MUTEX_LOCKER (locker, m_mutex); + // Make sure we have some bits and that they aren't already set... + if (m_bits != bits) + { + // Figure out which bits are changing + uint32_t changed_bits = m_bits ^ bits; + // Set the new bit values + m_bits = bits; + // If any new bits are set, then broadcast + if (changed_bits & m_bits) + m_set_condition.Broadcast(); + } +} + +// Set one or more event bits and broadcast if any new event bits get set +// that weren't already set. + +void +PThreadEvent::SetEvents(const uint32_t mask) +{ + // DNBLogThreadedIf(LOG_EVENTS, "%p PThreadEvent::%s (0x%8.8x)", this, __FUNCTION__, mask); + // Make sure we have some bits to set + if (mask) + { + PTHREAD_MUTEX_LOCKER (locker, m_mutex); + // Save the old event bit state so we can tell if things change + uint32_t old = m_bits; + // Set the all event bits that are set in 'mask' + m_bits |= mask; + // Broadcast only if any extra bits got set. + if (old != m_bits) + m_set_condition.Broadcast(); + } +} + +// Reset one or more event bits +void +PThreadEvent::ResetEvents(const uint32_t mask) +{ + // DNBLogThreadedIf(LOG_EVENTS, "%p PThreadEvent::%s (0x%8.8x)", this, __FUNCTION__, mask); + if (mask) + { + PTHREAD_MUTEX_LOCKER (locker, m_mutex); + + // Save the old event bit state so we can tell if things change + uint32_t old = m_bits; + // Clear the all event bits that are set in 'mask' + m_bits &= ~mask; + // Broadcast only if any extra bits got reset. + if (old != m_bits) + m_reset_condition.Broadcast(); + } +} + +//---------------------------------------------------------------------- +// Wait until 'timeout_abstime' for any events that are set in +// 'mask'. If 'timeout_abstime' is NULL, then wait forever. +//---------------------------------------------------------------------- +uint32_t +PThreadEvent::WaitForSetEvents(const uint32_t mask, const struct timespec *timeout_abstime) const +{ + // DNBLogThreadedIf(LOG_EVENTS, "%p PThreadEvent::%s (0x%8.8x, %p)", this, __FUNCTION__, mask, timeout_abstime); + int err = 0; + // pthread_cond_timedwait() or pthread_cond_wait() will atomically + // unlock the mutex and wait for the condition to be set. When either + // function returns, they will re-lock the mutex. We use an auto lock/unlock + // class (PThreadMutex::Locker) to allow us to return at any point in this + // function and not have to worry about unlocking the mutex. + PTHREAD_MUTEX_LOCKER (locker, m_mutex); + do + { + // Check our predicate (event bits) in case any are already set + if (mask & m_bits) + { + uint32_t bits_set = mask & m_bits; + // Our PThreadMutex::Locker will automatically unlock our mutex + return bits_set; + } + if (timeout_abstime) + { + // Wait for condition to get broadcast, or for a timeout. If we get + // a timeout we will drop out of the do loop and return false which + // is what we want. + err = ::pthread_cond_timedwait (m_set_condition.Condition(), m_mutex.Mutex(), timeout_abstime); + // Retest our predicate in case of a race condition right at the end + // of the timeout. + if (err == ETIMEDOUT) + { + uint32_t bits_set = mask & m_bits; + return bits_set; + } + } + else + { + // Wait for condition to get broadcast. The only error this function + // should return is if + err = ::pthread_cond_wait (m_set_condition.Condition(), m_mutex.Mutex()); + } + } while (err == 0); + return 0; +} + +//---------------------------------------------------------------------- +// Wait until 'timeout_abstime' for any events in 'mask' to reset. +// If 'timeout_abstime' is NULL, then wait forever. +//---------------------------------------------------------------------- +uint32_t +PThreadEvent::WaitForEventsToReset(const uint32_t mask, const struct timespec *timeout_abstime) const +{ + // DNBLogThreadedIf(LOG_EVENTS, "%p PThreadEvent::%s (0x%8.8x, %p)", this, __FUNCTION__, mask, timeout_abstime); + int err = 0; + // pthread_cond_timedwait() or pthread_cond_wait() will atomically + // unlock the mutex and wait for the condition to be set. When either + // function returns, they will re-lock the mutex. We use an auto lock/unlock + // class (PThreadMutex::Locker) to allow us to return at any point in this + // function and not have to worry about unlocking the mutex. + PTHREAD_MUTEX_LOCKER (locker, m_mutex); + do + { + // Check our predicate (event bits) each time through this do loop + if ((mask & m_bits) == 0) + { + // All the bits requested have been reset, return zero indicating + // which bits from the mask were still set (none of them) + return 0; + } + if (timeout_abstime) + { + // Wait for condition to get broadcast, or for a timeout. If we get + // a timeout we will drop out of the do loop and return false which + // is what we want. + err = ::pthread_cond_timedwait (m_reset_condition.Condition(), m_mutex.Mutex(), timeout_abstime); + } + else + { + // Wait for condition to get broadcast. The only error this function + // should return is if + err = ::pthread_cond_wait (m_reset_condition.Condition(), m_mutex.Mutex()); + } + } while (err == 0); + // Return a mask indicating which bits (if any) were still set + return mask & m_bits; +} + +uint32_t +PThreadEvent::WaitForResetAck (const uint32_t mask, const struct timespec *timeout_abstime) const +{ + if (mask & m_reset_ack_mask) + { + // DNBLogThreadedIf(LOG_EVENTS, "%p PThreadEvent::%s (0x%8.8x, %p)", this, __FUNCTION__, mask, timeout_abstime); + return WaitForEventsToReset (mask & m_reset_ack_mask, timeout_abstime); + } + return 0; +} diff --git a/lldb/tools/debugserver/source/PThreadEvent.h b/lldb/tools/debugserver/source/PThreadEvent.h new file mode 100644 index 00000000000..7928566e8b1 --- /dev/null +++ b/lldb/tools/debugserver/source/PThreadEvent.h @@ -0,0 +1,59 @@ +//===-- PThreadEvent.h ------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/16/07. +// +//===----------------------------------------------------------------------===// + +#ifndef __PThreadEvent_h__ +#define __PThreadEvent_h__ +#include "PThreadMutex.h" +#include "PThreadCondition.h" +#include <stdint.h> +#include <time.h> + +class PThreadEvent +{ +public: + PThreadEvent (uint32_t bits = 0, uint32_t validBits = 0); + ~PThreadEvent (); + + uint32_t NewEventBit (); + void FreeEventBits (const uint32_t mask); + + void ReplaceEventBits (const uint32_t bits); + uint32_t GetEventBits () const; + void SetEvents (const uint32_t mask); + void ResetEvents (const uint32_t mask); + // Wait for events to be set or reset. These functions take an optional + // timeout value. If timeout is NULL an infinite timeout will be used. + uint32_t WaitForSetEvents (const uint32_t mask, const struct timespec *timeout_abstime = NULL) const; + uint32_t WaitForEventsToReset(const uint32_t mask, const struct timespec *timeout_abstime = NULL) const; + + uint32_t GetResetAckMask () const { return m_reset_ack_mask; } + uint32_t SetResetAckMask (uint32_t mask) { return m_reset_ack_mask = mask; } + uint32_t WaitForResetAck (const uint32_t mask, const struct timespec *timeout_abstime = NULL) const; +protected: + //---------------------------------------------------------------------- + // pthread condition and mutex variable to controll access and allow + // blocking between the main thread and the spotlight index thread. + //---------------------------------------------------------------------- + mutable PThreadMutex m_mutex; + mutable PThreadCondition m_set_condition; + mutable PThreadCondition m_reset_condition; + uint32_t m_bits; + uint32_t m_validBits; + uint32_t m_reset_ack_mask; +private: + PThreadEvent(const PThreadEvent&); // Outlaw copy contructor + PThreadEvent& operator=(const PThreadEvent& rhs); + +}; + +#endif // #ifndef __PThreadEvent_h__ diff --git a/lldb/tools/debugserver/source/PThreadMutex.cpp b/lldb/tools/debugserver/source/PThreadMutex.cpp new file mode 100644 index 00000000000..bd91ed0154b --- /dev/null +++ b/lldb/tools/debugserver/source/PThreadMutex.cpp @@ -0,0 +1,84 @@ +//===-- PThreadMutex.cpp ----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 12/9/08. +// +//===----------------------------------------------------------------------===// + +#include "PThreadMutex.h" + +// C Includes +// C++ Includes +// Other libraries and framework includes +// Project includes +#include "DNBTimer.h" + +#if defined (DEBUG_PTHREAD_MUTEX_DEADLOCKS) + +PThreadMutex::Locker::Locker(PThreadMutex& m, const char *function, const char *file, const int line) : + m_pMutex(m.Mutex()), + m_function(function), + m_file(file), + m_line(line), + m_lock_time(0) +{ + Lock(); +} + +PThreadMutex::Locker::Locker(PThreadMutex* m, const char *function, const char *file, const int line) : + m_pMutex(m ? m->Mutex() : NULL), + m_function(function), + m_file(file), + m_line(line), + m_lock_time(0) +{ + Lock(); +} + +PThreadMutex::Locker::Locker(pthread_mutex_t *mutex, const char *function, const char *file, const int line) : + m_pMutex(mutex), + m_function(function), + m_file(file), + m_line(line), + m_lock_time(0) +{ + Lock(); +} + + +PThreadMutex::Locker::~Locker() +{ + Unlock(); +} + + +void +PThreadMutex::Locker::Lock() +{ + if (m_pMutex) + { + m_lock_time = DNBTimer::GetTimeOfDay(); + if (::pthread_mutex_trylock (m_pMutex) != 0) + { + fprintf(stdout, "::pthread_mutex_trylock (%8.8p) mutex is locked (function %s in %s:%i), waiting...\n", m_pMutex, m_function, m_file, m_line); + ::pthread_mutex_lock (m_pMutex); + fprintf(stdout, "::pthread_mutex_lock (%8.8p) succeeded after %6llu usecs (function %s in %s:%i)\n", m_pMutex, DNBTimer::GetTimeOfDay() - m_lock_time, m_function, m_file, m_line); + } + } +} + + +void +PThreadMutex::Locker::Unlock() +{ + fprintf(stdout, "::pthread_mutex_unlock (%8.8p) had lock for %6llu usecs in %s in %s:%i\n", m_pMutex, DNBTimer::GetTimeOfDay() - m_lock_time, m_function, m_file, m_line); + ::pthread_mutex_unlock (m_pMutex); +} + +#endif diff --git a/lldb/tools/debugserver/source/PThreadMutex.h b/lldb/tools/debugserver/source/PThreadMutex.h new file mode 100644 index 00000000000..9a12f6e8e03 --- /dev/null +++ b/lldb/tools/debugserver/source/PThreadMutex.h @@ -0,0 +1,148 @@ +//===-- PThreadMutex.h ------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/16/07. +// +//===----------------------------------------------------------------------===// + +#ifndef __PThreadMutex_h__ +#define __PThreadMutex_h__ + +#include <pthread.h> +#include <assert.h> +#include <stdint.h> + +//#define DEBUG_PTHREAD_MUTEX_DEADLOCKS 1 + +#if defined (DEBUG_PTHREAD_MUTEX_DEADLOCKS) +#define PTHREAD_MUTEX_LOCKER(var, mutex) PThreadMutex::Locker var(mutex, __FUNCTION__, __FILE__, __LINE__) + +#else +#define PTHREAD_MUTEX_LOCKER(var, mutex) PThreadMutex::Locker var(mutex) +#endif + +class PThreadMutex +{ +public: + + class Locker + { + public: +#if defined (DEBUG_PTHREAD_MUTEX_DEADLOCKS) + + Locker(PThreadMutex& m, const char *function, const char *file, int line); + Locker(PThreadMutex* m, const char *function, const char *file, int line); + Locker(pthread_mutex_t *mutex, const char *function, const char *file, int line); + ~Locker(); + void Lock(); + void Unlock(); + +#else + Locker(PThreadMutex& m) : + m_pMutex(m.Mutex()) + { + Lock(); + } + + Locker(PThreadMutex* m) : + m_pMutex(m ? m->Mutex() : NULL) + { + Lock(); + } + + Locker(pthread_mutex_t *mutex) : + m_pMutex(mutex) + { + Lock(); + } + + void Lock() + { + if (m_pMutex) + ::pthread_mutex_lock (m_pMutex); + } + + void Unlock() + { + if (m_pMutex) + ::pthread_mutex_unlock (m_pMutex); + } + + ~Locker() + { + Unlock(); + } + +#endif + + // unlock any the current mutex and lock the new one if it is valid + void Reset(pthread_mutex_t *pMutex = NULL) + { + Unlock(); + m_pMutex = pMutex; + Lock(); + } + pthread_mutex_t *m_pMutex; +#if defined (DEBUG_PTHREAD_MUTEX_DEADLOCKS) + const char *m_function; + const char *m_file; + int m_line; + uint64_t m_lock_time; +#endif + }; + + + PThreadMutex() + { + int err; + err = ::pthread_mutex_init (&m_mutex, NULL); assert(err == 0); + } + + PThreadMutex(int type) + { + int err; + ::pthread_mutexattr_t attr; + err = ::pthread_mutexattr_init (&attr); assert(err == 0); + err = ::pthread_mutexattr_settype (&attr, type); assert(err == 0); + err = ::pthread_mutex_init (&m_mutex, &attr); assert(err == 0); + err = ::pthread_mutexattr_destroy (&attr); assert(err == 0); + } + + ~PThreadMutex() + { + int err; + err = ::pthread_mutex_destroy (&m_mutex); + if (err != 0) + { + err = Unlock(); + if (err == 0) + ::pthread_mutex_destroy (&m_mutex); + } + } + + pthread_mutex_t *Mutex() + { + return &m_mutex; + } + + int Lock() + { + return ::pthread_mutex_lock (&m_mutex); + } + + int Unlock() + { + return ::pthread_mutex_unlock (&m_mutex); + } + +protected: + pthread_mutex_t m_mutex; +}; + +#endif diff --git a/lldb/tools/debugserver/source/ProfileObjectiveC.cpp b/lldb/tools/debugserver/source/ProfileObjectiveC.cpp new file mode 100644 index 00000000000..1fd2d526d25 --- /dev/null +++ b/lldb/tools/debugserver/source/ProfileObjectiveC.cpp @@ -0,0 +1,393 @@ +//===-- ProfileObjectiveC.cpp -----------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 10/4/07. +// +//===----------------------------------------------------------------------===// + +#include "ProfileObjectiveC.h" +#include "DNB.h" +#include <objc/objc-runtime.h> +#include <map> + +#if defined (__powerpc__) || defined (__ppc__) +#define OBJC_MSG_SEND_PPC32_COMM_PAGE_ADDR ((nub_addr_t)0xfffeff00) +#endif + +//---------------------------------------------------------------------- +// Constructor +//---------------------------------------------------------------------- +ProfileObjectiveC::ProfileObjectiveC() : + m_pid(INVALID_NUB_PROCESS), + m_objcStats(), + m_hit_count(0), + m_dump_count(0xffff) +{ + memset(&m_begin_time, 0, sizeof(m_begin_time)); +} + +//---------------------------------------------------------------------- +// Destructor +//---------------------------------------------------------------------- +ProfileObjectiveC::~ProfileObjectiveC() +{ +} + +//---------------------------------------------------------------------- +// Clear any counts that we may have had +//---------------------------------------------------------------------- +void +ProfileObjectiveC::Clear() +{ + if (m_pid != INVALID_NUB_PROCESS) + { + DNBBreakpointClear(m_pid, m_objc_msgSend.breakID); + DNBBreakpointClear(m_pid, m_objc_msgSendSuper.breakID); +#if defined (__powerpc__) || defined (__ppc__) + DNBBreakpointClear(m_pid, m_objc_msgSend_rtp.breakID); +#endif + } + m_objc_msgSend.Clear(); + m_objc_msgSendSuper.Clear(); +#if defined (__powerpc__) || defined (__ppc__) + memset(m_objc_msgSend_opcode, 0, k_opcode_size); + m_objc_msgSend_rtp.Clear(); +#endif + memset(&m_begin_time, 0, sizeof(m_begin_time)); + m_objcStats.clear(); +} + +void +ProfileObjectiveC::Initialize(nub_process_t pid) +{ + Clear(); + m_pid = pid; +} + + +void +ProfileObjectiveC::ProcessStateChanged(nub_state_t state) +{ + //printf("ProfileObjectiveC::%s(%s)\n", __FUNCTION__, DNBStateAsString(state)); + switch (state) + { + case eStateInvalid: + case eStateUnloaded: + case eStateExited: + case eStateDetached: + Clear(); + break; + + case eStateStopped: +#if defined (__powerpc__) || defined (__ppc__) + if (NUB_BREAK_ID_IS_VALID(m_objc_msgSend.breakID) && !NUB_BREAK_ID_IS_VALID(m_objc_msgSend_rtp.breakID)) + { + nub_thread_t tid = DNBProcessGetCurrentThread(m_pid); + DNBRegisterValue pc_value; + if (DNBThreadGetRegisterValueByName(m_pid, tid, REGISTER_SET_ALL, "srr0" , &pc_value)) + { + nub_addr_t pc = pc_value.value.uint32; + if (pc == OBJC_MSG_SEND_PPC32_COMM_PAGE_ADDR) + { + // Restore previous first instruction to 0xfffeff00 in comm page + DNBProcessMemoryWrite(m_pid, OBJC_MSG_SEND_PPC32_COMM_PAGE_ADDR, k_opcode_size, m_objc_msgSend_opcode); + //printf("Setting breakpoint on _objc_msgSend_rtp...\n"); + m_objc_msgSend_rtp.breakID = DNBBreakpointSet(m_pid, OBJC_MSG_SEND_PPC32_COMM_PAGE_ADDR); + if (NUB_BREAK_ID_IS_VALID(m_objc_msgSend_rtp.breakID)) + { + DNBBreakpointSetCallback(m_pid, m_objc_msgSend_rtp.breakID, ProfileObjectiveC::MessageSendBreakpointCallback, this); + } + } + } + } +#endif + DumpStats(m_pid, stdout); + break; + + case eStateAttaching: + case eStateLaunching: + case eStateRunning: + case eStateStepping: + case eStateCrashed: + case eStateSuspended: + break; + + default: + break; + } +} + +void +ProfileObjectiveC::SharedLibraryStateChanged(DNBExecutableImageInfo *image_infos, nub_size_t num_image_infos) +{ + //printf("ProfileObjectiveC::%s(%p, %u)\n", __FUNCTION__, image_infos, num_image_infos); + if (m_objc_msgSend.IsValid() && m_objc_msgSendSuper.IsValid()) + return; + + if (image_infos) + { + nub_process_t pid = m_pid; + nub_size_t i; + for (i = 0; i < num_image_infos; i++) + { + if (strcmp(image_infos[i].name, "/usr/lib/libobjc.A.dylib") == 0) + { + if (!NUB_BREAK_ID_IS_VALID(m_objc_msgSend.breakID)) + { + m_objc_msgSend.addr = DNBProcessLookupAddress(pid, "_objc_msgSend", image_infos[i].name); + + if (m_objc_msgSend.addr != INVALID_NUB_ADDRESS) + { +#if defined (__powerpc__) || defined (__ppc__) + if (DNBProcessMemoryRead(pid, m_objc_msgSend.addr, k_opcode_size, m_objc_msgSend_opcode) != k_opcode_size) + memset(m_objc_msgSend_opcode, 0, sizeof(m_objc_msgSend_opcode)); +#endif + m_objc_msgSend.breakID = DNBBreakpointSet(pid, m_objc_msgSend.addr, 4, false); + if (NUB_BREAK_ID_IS_VALID(m_objc_msgSend.breakID)) + DNBBreakpointSetCallback(pid, m_objc_msgSend.breakID, ProfileObjectiveC::MessageSendBreakpointCallback, this); + } + } + + if (!NUB_BREAK_ID_IS_VALID(m_objc_msgSendSuper.breakID)) + { + m_objc_msgSendSuper.addr = DNBProcessLookupAddress(pid, "_objc_msgSendSuper", image_infos[i].name); + + if (m_objc_msgSendSuper.addr != INVALID_NUB_ADDRESS) + { + m_objc_msgSendSuper.breakID = DNBBreakpointSet(pid, m_objc_msgSendSuper.addr, 4, false); + if (NUB_BREAK_ID_IS_VALID(m_objc_msgSendSuper.breakID)) + DNBBreakpointSetCallback(pid, m_objc_msgSendSuper.breakID, ProfileObjectiveC::MessageSendSuperBreakpointCallback, this); + } + } + break; + } + } + } +} + + +void +ProfileObjectiveC::SetStartTime() +{ + gettimeofday(&m_begin_time, NULL); +} + +void +ProfileObjectiveC::SelectorHit(objc_class_ptr_t isa, objc_selector_t sel) +{ + m_objcStats[isa][sel]++; +} + +nub_bool_t +ProfileObjectiveC::MessageSendBreakpointCallback(nub_process_t pid, nub_thread_t tid, nub_break_t breakID, void *userData) +{ + ProfileObjectiveC *profile_objc = (ProfileObjectiveC*)userData; + uint32_t hit_count = profile_objc->IncrementHitCount(); + if (hit_count == 1) + profile_objc->SetStartTime(); + + objc_class_ptr_t objc_self = 0; + objc_selector_t objc_selector = 0; +#if defined (__i386__) + DNBRegisterValue esp; + if (DNBThreadGetRegisterValueByName(pid, tid, REGISTER_SET_ALL, "esp", &esp)) + { + uint32_t uval32[2]; + if (DNBProcessMemoryRead(pid, esp.value.uint32 + 4, 8, &uval32) == 8) + { + objc_self = uval32[0]; + objc_selector = uval32[1]; + } + } +#elif defined (__powerpc__) || defined (__ppc__) + DNBRegisterValue r3; + DNBRegisterValue r4; + if (DNBThreadGetRegisterValueByName(pid, tid, REGISTER_SET_ALL, "r3", &r3) && + DNBThreadGetRegisterValueByName(pid, tid, REGISTER_SET_ALL, "r4", &r4)) + { + objc_self = r3.value.uint32; + objc_selector = r4.value.uint32; + } +#elif defined (__arm__) + DNBRegisterValue r0; + DNBRegisterValue r1; + if (DNBThreadGetRegisterValueByName(pid, tid, REGISTER_SET_ALL, "r0", &r0) && + DNBThreadGetRegisterValueByName(pid, tid, REGISTER_SET_ALL, "r1", &r1)) + { + objc_self = r0.value.uint32; + objc_selector = r1.value.uint32; + } +#else +#error undefined architecture +#endif + if (objc_selector != 0) + { + uint32_t isa = 0; + if (objc_self == 0) + { + profile_objc->SelectorHit(0, objc_selector); + } + else + if (DNBProcessMemoryRead(pid, (nub_addr_t)objc_self, sizeof(isa), &isa) == sizeof(isa)) + { + if (isa) + { + profile_objc->SelectorHit(isa, objc_selector); + } + else + { + profile_objc->SelectorHit(0, objc_selector); + } + } + } + + + // Dump stats if we are supposed to + if (profile_objc->ShouldDumpStats()) + { + profile_objc->DumpStats(pid, stdout); + return true; + } + + // Just let the target run again by returning false; + return false; +} + +nub_bool_t +ProfileObjectiveC::MessageSendSuperBreakpointCallback(nub_process_t pid, nub_thread_t tid, nub_break_t breakID, void *userData) +{ + ProfileObjectiveC *profile_objc = (ProfileObjectiveC*)userData; + + uint32_t hit_count = profile_objc->IncrementHitCount(); + if (hit_count == 1) + profile_objc->SetStartTime(); + +// printf("BreakID %u hit count is = %u\n", breakID, hc); + objc_class_ptr_t objc_super = 0; + objc_selector_t objc_selector = 0; +#if defined (__i386__) + DNBRegisterValue esp; + if (DNBThreadGetRegisterValueByName(pid, tid, REGISTER_SET_ALL, "esp", &esp)) + { + uint32_t uval32[2]; + if (DNBProcessMemoryRead(pid, esp.value.uint32 + 4, 8, &uval32) == 8) + { + objc_super = uval32[0]; + objc_selector = uval32[1]; + } + } +#elif defined (__powerpc__) || defined (__ppc__) + DNBRegisterValue r3; + DNBRegisterValue r4; + if (DNBThreadGetRegisterValueByName(pid, tid, REGISTER_SET_ALL, "r3", &r3) && + DNBThreadGetRegisterValueByName(pid, tid, REGISTER_SET_ALL, "r4", &r4)) + { + objc_super = r3.value.uint32; + objc_selector = r4.value.uint32; + } +#elif defined (__arm__) + DNBRegisterValue r0; + DNBRegisterValue r1; + if (DNBThreadGetRegisterValueByName(pid, tid, REGISTER_SET_ALL, "r0", &r0) && + DNBThreadGetRegisterValueByName(pid, tid, REGISTER_SET_ALL, "r1", &r1)) + { + objc_super = r0.value.uint32; + objc_selector = r1.value.uint32; + } +#else +#error undefined architecture +#endif + if (objc_selector != 0) + { + uint32_t isa = 0; + if (objc_super == 0) + { + profile_objc->SelectorHit(0, objc_selector); + } + else + if (DNBProcessMemoryRead(pid, (nub_addr_t)objc_super + 4, sizeof(isa), &isa) == sizeof(isa)) + { + if (isa) + { + profile_objc->SelectorHit(isa, objc_selector); + } + else + { + profile_objc->SelectorHit(0, objc_selector); + } + } + } + + // Dump stats if we are supposed to + if (profile_objc->ShouldDumpStats()) + { + profile_objc->DumpStats(pid, stdout); + return true; + } + + // Just let the target run again by returning false; + return false; +} + +void +ProfileObjectiveC::DumpStats(nub_process_t pid, FILE *f) +{ + if (f == NULL) + return; + + if (m_hit_count == 0) + return; + + ClassStatsMap::iterator class_pos; + ClassStatsMap::iterator class_end = m_objcStats.end(); + + struct timeval end_time; + gettimeofday(&end_time, NULL); + int64_t elapsed_usec = ((int64_t)(1000*1000))*((int64_t)end_time.tv_sec - (int64_t)m_begin_time.tv_sec) + ((int64_t)end_time.tv_usec - (int64_t)m_begin_time.tv_usec); + fprintf(f, "%u probe hits for %.2f hits/sec)\n", m_hit_count, (double)m_hit_count / (((double)elapsed_usec)/(1000000.0))); + + for (class_pos = m_objcStats.begin(); class_pos != class_end; ++class_pos) + { + SelectorHitCount::iterator sel_pos; + SelectorHitCount::iterator sel_end = class_pos->second.end(); + for (sel_pos = class_pos->second.begin(); sel_pos != sel_end; ++sel_pos) + { + struct objc_class objc_class; + uint32_t isa = class_pos->first; + uint32_t sel = sel_pos->first; + uint32_t sel_hit_count = sel_pos->second; + + if (isa != 0 && DNBProcessMemoryRead(pid, isa, sizeof(objc_class), &objc_class) == sizeof(objc_class)) + { + /* fprintf(f, "%#.8x\n isa = %p\n super_class = %p\n name = %p\n version = %lx\n info = %lx\ninstance_size = %lx\n ivars = %p\n methodLists = %p\n cache = %p\n protocols = %p\n", + arg1.value.pointer, + objc_class.isa, + objc_class.super_class, + objc_class.name, + objc_class.version, + objc_class.info, + objc_class.instance_size, + objc_class.ivars, + objc_class.methodLists, + objc_class.cache, + objc_class.protocols); */ + + // Print the class name + fprintf(f, "%6u hits for %c[", sel_hit_count, (objc_class.super_class == objc_class.isa ? '+' : '-')); + DNBPrintf(pid, INVALID_NUB_THREAD, (nub_addr_t)objc_class.name, f, "%s "); + } + else + { + fprintf(f, "%6u hits for [<nil> ", sel_hit_count); + } + DNBPrintf(pid, INVALID_NUB_THREAD, sel, f, "%s]\n"); + } + } +} + diff --git a/lldb/tools/debugserver/source/ProfileObjectiveC.h b/lldb/tools/debugserver/source/ProfileObjectiveC.h new file mode 100644 index 00000000000..8a5c13db32a --- /dev/null +++ b/lldb/tools/debugserver/source/ProfileObjectiveC.h @@ -0,0 +1,82 @@ +//===-- ProfileObjectiveC.h -------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 10/4/07. +// +//===----------------------------------------------------------------------===// + +#ifndef __ProfileObjectiveC_h__ +#define __ProfileObjectiveC_h__ + +#include "DNB.h" +#include "DNBRuntimeAction.h" +#include <map> +#include <sys/time.h> + +class ProfileObjectiveC : public DNBRuntimeAction +{ +public: + ProfileObjectiveC(); + virtual ~ProfileObjectiveC(); + //------------------------------------------------------------------ + // DNBRuntimeAction required functions + //------------------------------------------------------------------ + virtual void Initialize(nub_process_t pid); + virtual void ProcessStateChanged(nub_state_t state); + virtual void SharedLibraryStateChanged(DNBExecutableImageInfo *image_infos, nub_size_t num_image_infos); + +protected: + typedef uint32_t objc_selector_t; + typedef uint32_t objc_class_ptr_t; + void Clear(); + static nub_bool_t MessageSendBreakpointCallback(nub_process_t pid, nub_thread_t tid, nub_break_t breakID, void *userData); + static nub_bool_t MessageSendSuperBreakpointCallback(nub_process_t pid, nub_thread_t tid, nub_break_t breakID, void *userData); + void DumpStats(nub_process_t pid, FILE *f); + void SetStartTime(); + void SelectorHit(objc_class_ptr_t isa, objc_selector_t sel); + typedef std::map<objc_selector_t, uint32_t> SelectorHitCount; + typedef std::map<objc_class_ptr_t, SelectorHitCount> ClassStatsMap; + typedef struct Probe + { + nub_addr_t addr; + nub_break_t breakID; + Probe() : addr(INVALID_NUB_ADDRESS), breakID(INVALID_NUB_BREAK_ID) {} + void Clear() + { + addr = INVALID_NUB_ADDRESS; + breakID = INVALID_NUB_BREAK_ID; + } + bool IsValid() const + { + return (addr != INVALID_NUB_ADDRESS) && (NUB_BREAK_ID_IS_VALID(breakID)); + } + }; + + uint32_t IncrementHitCount() { return ++m_hit_count; } + bool ShouldDumpStats() const { return m_dump_count && (m_hit_count % m_dump_count) == 0; } + + nub_process_t m_pid; + Probe m_objc_msgSend; + Probe m_objc_msgSendSuper; + uint32_t m_hit_count; // Number of times we have gotten one of our breakpoints hit + uint32_t m_dump_count; // Dump stats every time the hit count reaches a multiple of this value +#if defined (__powerpc__) || defined (__ppc__) + enum + { + k_opcode_size = 4 + }; + uint8_t m_objc_msgSend_opcode[k_opcode_size]; // Saved copy of first opcode in objc_msgSend + Probe m_objc_msgSend_rtp; // COMM page probe info for objc_msgSend +#endif + struct timeval m_begin_time; + ClassStatsMap m_objcStats; +}; + + +#endif // #ifndef __ProfileObjectiveC_h__ diff --git a/lldb/tools/debugserver/source/PseudoTerminal.cpp b/lldb/tools/debugserver/source/PseudoTerminal.cpp new file mode 100644 index 00000000000..278ab88828a --- /dev/null +++ b/lldb/tools/debugserver/source/PseudoTerminal.cpp @@ -0,0 +1,226 @@ +//===-- PseudoTerminal.cpp --------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 1/8/08. +// +//===----------------------------------------------------------------------===// + +#include "PseudoTerminal.h" +#include <stdlib.h> +#include <sys/ioctl.h> + +//---------------------------------------------------------------------- +// PseudoTerminal constructor +//---------------------------------------------------------------------- +PseudoTerminal::PseudoTerminal() : + m_master_fd(invalid_fd), + m_slave_fd(invalid_fd) +{ +} + +//---------------------------------------------------------------------- +// Destructor +// The master and slave file descriptors will get closed if they are +// valid. Call the ReleaseMasterFD()/ReleaseSlaveFD() member functions +// to release any file descriptors that are needed beyond the lifespan +// of this object. +//---------------------------------------------------------------------- +PseudoTerminal::~PseudoTerminal() +{ + CloseMaster(); + CloseSlave(); +} + +//---------------------------------------------------------------------- +// Close the master file descriptor if it is valid. +//---------------------------------------------------------------------- +void +PseudoTerminal::CloseMaster() +{ + if (m_master_fd > 0) + { + ::close (m_master_fd); + m_master_fd = invalid_fd; + } +} + +//---------------------------------------------------------------------- +// Close the slave file descriptor if it is valid. +//---------------------------------------------------------------------- +void +PseudoTerminal::CloseSlave() +{ + if (m_slave_fd > 0) + { + ::close (m_slave_fd); + m_slave_fd = invalid_fd; + } +} + +//---------------------------------------------------------------------- +// Open the first available pseudo terminal with OFLAG as the +// permissions. The file descriptor is store in the m_master_fd member +// variable and can be accessed via the MasterFD() or ReleaseMasterFD() +// accessors. +// +// Suggested value for oflag is O_RDWR|O_NOCTTY +// +// RETURNS: +// Zero when successful, non-zero indicating an error occurred. +//---------------------------------------------------------------------- +PseudoTerminal::Error +PseudoTerminal::OpenFirstAvailableMaster(int oflag) +{ + // Open the master side of a pseudo terminal + m_master_fd = ::posix_openpt (oflag); + if (m_master_fd < 0) + { + return err_posix_openpt_failed; + } + + // Grant access to the slave pseudo terminal + if (::grantpt (m_master_fd) < 0) + { + CloseMaster(); + return err_grantpt_failed; + } + + // Clear the lock flag on the slave pseudo terminal + if (::unlockpt (m_master_fd) < 0) + { + CloseMaster(); + return err_unlockpt_failed; + } + + return success; +} + +//---------------------------------------------------------------------- +// Open the slave pseudo terminal for the current master pseudo +// terminal. A master pseudo terminal should already be valid prior to +// calling this function (see PseudoTerminal::OpenFirstAvailableMaster()). +// The file descriptor is stored in the m_slave_fd member variable and +// can be accessed via the SlaveFD() or ReleaseSlaveFD() accessors. +// +// RETURNS: +// Zero when successful, non-zero indicating an error occurred. +//---------------------------------------------------------------------- +PseudoTerminal::Error +PseudoTerminal::OpenSlave(int oflag) +{ + CloseSlave(); + + // Open the master side of a pseudo terminal + const char *slave_name = SlaveName(); + + if (slave_name == NULL) + return err_ptsname_failed; + + m_slave_fd = ::open (slave_name, oflag); + + if (m_slave_fd < 0) + return err_open_slave_failed; + + return success; +} + + + +//---------------------------------------------------------------------- +// Get the name of the slave pseudo terminal. A master pseudo terminal +// should already be valid prior to calling this function (see +// PseudoTerminal::OpenFirstAvailableMaster()). +// +// RETURNS: +// NULL if no valid master pseudo terminal or if ptsname() fails. +// The name of the slave pseudo terminal as a NULL terminated C string +// that comes from static memory, so a copy of the string should be +// made as subsequent calls can change this value. +//---------------------------------------------------------------------- +const char* +PseudoTerminal::SlaveName() const +{ + if (m_master_fd < 0) + return NULL; + return ::ptsname (m_master_fd); +} + + +//---------------------------------------------------------------------- +// Fork a child process that and have its stdio routed to a pseudo +// terminal. +// +// In the parent process when a valid pid is returned, the master file +// descriptor can be used as a read/write access to stdio of the +// child process. +// +// In the child process the stdin/stdout/stderr will already be routed +// to the slave pseudo terminal and the master file descriptor will be +// closed as it is no longer needed by the child process. +// +// This class will close the file descriptors for the master/slave +// when the destructor is called, so be sure to call ReleaseMasterFD() +// or ReleaseSlaveFD() if any file descriptors are going to be used +// past the lifespan of this object. +// +// RETURNS: +// in the parent process: the pid of the child, or -1 if fork fails +// in the child process: zero +//---------------------------------------------------------------------- + +pid_t +PseudoTerminal::Fork(PseudoTerminal::Error& error) +{ + pid_t pid = invalid_pid; + error = OpenFirstAvailableMaster (O_RDWR|O_NOCTTY); + + if (error == 0) + { + // Successfully opened our master pseudo terminal + + pid = ::fork (); + if (pid < 0) + { + // Fork failed + error = err_fork_failed; + } + else if (pid == 0) + { + // Child Process + ::setsid(); + + error = OpenSlave (O_RDWR); + if (error == 0) + { + // Successfully opened slave + // We are done with the master in the child process so lets close it + CloseMaster (); + +#if defined (TIOCSCTTY) + // Acquire the controlling terminal + if (::ioctl (m_slave_fd, TIOCSCTTY, (char *)0) < 0) + error = err_failed_to_acquire_controlling_terminal; +#endif + // Duplicate all stdio file descriptors to the slave pseudo terminal + if (::dup2 (m_slave_fd, STDIN_FILENO) != STDIN_FILENO) + error = error ? error : err_dup2_failed_on_stdin; + if (::dup2 (m_slave_fd, STDOUT_FILENO) != STDOUT_FILENO) + error = error ? error : err_dup2_failed_on_stdout; + if (::dup2 (m_slave_fd, STDERR_FILENO) != STDERR_FILENO) + error = error ? error : err_dup2_failed_on_stderr; + } + } + else + { + // Parent Process + // Do nothing and let the pid get returned! + } + } + return pid; +} diff --git a/lldb/tools/debugserver/source/PseudoTerminal.h b/lldb/tools/debugserver/source/PseudoTerminal.h new file mode 100644 index 00000000000..1f09b417452 --- /dev/null +++ b/lldb/tools/debugserver/source/PseudoTerminal.h @@ -0,0 +1,94 @@ +//===-- PseudoTerminal.h ----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 1/8/08. +// +//===----------------------------------------------------------------------===// + +#ifndef __PseudoTerminal_h__ +#define __PseudoTerminal_h__ + +#include <fcntl.h> +#include <termios.h> +#include <string> + +class PseudoTerminal +{ +public: + enum { + invalid_fd = -1, + invalid_pid = -1 + }; + + typedef enum Error + { + success = 0, + err_posix_openpt_failed = -2, + err_grantpt_failed = -3, + err_unlockpt_failed = -4, + err_ptsname_failed = -5, + err_open_slave_failed = -6, + err_fork_failed = -7, + err_setsid_failed = -8, + err_failed_to_acquire_controlling_terminal = -9, + err_dup2_failed_on_stdin = -10, + err_dup2_failed_on_stdout = -11, + err_dup2_failed_on_stderr = -12 + }; + //------------------------------------------------------------------ + // Constructors and Destructors + //------------------------------------------------------------------ + PseudoTerminal (); + ~PseudoTerminal (); + + void CloseMaster (); + void CloseSlave (); + Error OpenFirstAvailableMaster (int oflag); + Error OpenSlave (int oflag); + int MasterFD () const { return m_master_fd; } + int SlaveFD () const { return m_slave_fd; } + int ReleaseMasterFD () + { + // Release ownership of the master pseudo terminal file + // descriptor without closing it. (the destructor for this + // class will close it otherwise!) + int fd = m_master_fd; + m_master_fd = invalid_fd; + return fd; + } + int ReleaseSlaveFD () + { + // Release ownership of the slave pseudo terminal file + // descriptor without closing it (the destructor for this + // class will close it otherwise!) + int fd = m_slave_fd; + m_slave_fd = invalid_fd; + return fd; + } + + const char* SlaveName () const; + + pid_t Fork(Error& error); +protected: + //------------------------------------------------------------------ + // Classes that inherit from PseudoTerminal can see and modify these + //------------------------------------------------------------------ + int m_master_fd; + int m_slave_fd; + +private: + //------------------------------------------------------------------ + // Outlaw copy and assignment constructors + //------------------------------------------------------------------ + PseudoTerminal(const PseudoTerminal& rhs); + PseudoTerminal& operator=(const PseudoTerminal& rhs); + +}; + +#endif // #ifndef __PseudoTerminal_h__ diff --git a/lldb/tools/debugserver/source/RNBContext.cpp b/lldb/tools/debugserver/source/RNBContext.cpp new file mode 100644 index 00000000000..7ef9d32c74e --- /dev/null +++ b/lldb/tools/debugserver/source/RNBContext.cpp @@ -0,0 +1,230 @@ +//===-- RNBContext.cpp ------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 12/12/07. +// +//===----------------------------------------------------------------------===// + +#include "RNBContext.h" +#include "RNBRemote.h" +#include "DNB.h" +#include "DNBLog.h" +#include "CFString.h" +#include <sstream> + +//---------------------------------------------------------------------- +// Destructor +//---------------------------------------------------------------------- +RNBContext::~RNBContext() +{ + SetProcessID (INVALID_NUB_PROCESS); +} + +//---------------------------------------------------------------------- +// RNBContext constructor +//---------------------------------------------------------------------- + +const char * +RNBContext::EnvironmentAtIndex (int index) +{ + if (index < m_env_vec.size()) + return m_env_vec[index].c_str(); + else + return NULL; +} + + +const char * +RNBContext::ArgumentAtIndex (int index) +{ + if (index < m_arg_vec.size()) + return m_arg_vec[index].c_str(); + else + return NULL; +} + +void +RNBContext::SetProcessID (nub_process_t pid) +{ + // Delete and events we created + if (m_pid != INVALID_NUB_PROCESS) + { + StopProcessStatusThread (); + // Unregister this context as a client of the process's events. + } + // Assign our new process ID + m_pid = pid; + + if (pid != INVALID_NUB_PROCESS) + { + StartProcessStatusThread (); + } +} + +void +RNBContext::StartProcessStatusThread() +{ + DNBLogThreadedIf(LOG_RNB_PROC, "RNBContext::%s called", __FUNCTION__); + if ((m_events.GetEventBits() & event_proc_thread_running) == 0) + { + int err = ::pthread_create (&m_pid_pthread, NULL, ThreadFunctionProcessStatus, this); + if (err == 0) + { + // Our thread was successfully kicked off, wait for it to + // set the started event so we can safely continue + m_events.WaitForSetEvents (event_proc_thread_running); + DNBLogThreadedIf(LOG_RNB_PROC, "RNBContext::%s thread got started!", __FUNCTION__); + } + else + { + DNBLogThreadedIf(LOG_RNB_PROC, "RNBContext::%s thread failed to start: err = %i", __FUNCTION__, err); + m_events.ResetEvents (event_proc_thread_running); + m_events.SetEvents (event_proc_thread_exiting); + } + } +} + +void +RNBContext::StopProcessStatusThread() +{ + DNBLogThreadedIf(LOG_RNB_PROC, "RNBContext::%s called", __FUNCTION__); + if ((m_events.GetEventBits() & event_proc_thread_running) == event_proc_thread_running) + { + struct timespec timeout_abstime; + DNBTimer::OffsetTimeOfDay(&timeout_abstime, 2, 0); + // Wait for 2 seconds for the rx thread to exit + if (m_events.WaitForSetEvents(RNBContext::event_proc_thread_exiting, &timeout_abstime) == RNBContext::event_proc_thread_exiting) + { + DNBLogThreadedIf(LOG_RNB_PROC, "RNBContext::%s thread stopped as requeseted", __FUNCTION__); + } + else + { + DNBLogThreadedIf(LOG_RNB_PROC, "RNBContext::%s thread did not stop in 2 seconds...", __FUNCTION__); + // Kill the RX thread??? + } + } +} + +//---------------------------------------------------------------------- +// This thread's sole purpose is to watch for any status changes in the +// child process. +//---------------------------------------------------------------------- +void* +RNBContext::ThreadFunctionProcessStatus(void *arg) +{ + RNBRemoteSP remoteSP(g_remoteSP); + RNBRemote* remote = remoteSP.get(); + if (remote == NULL) + return NULL; + RNBContext& ctx = remote->Context(); + + nub_process_t pid = ctx.ProcessID(); + DNBLogThreadedIf(LOG_RNB_PROC, "RNBContext::%s (arg=%p, pid=%4.4x): thread starting...", __FUNCTION__, arg, pid); + ctx.Events().SetEvents (RNBContext::event_proc_thread_running); + bool done = false; + while (!done) + { + DNBLogThreadedIf(LOG_RNB_PROC, "RNBContext::%s calling DNBProcessWaitForEvent(pid, eEventProcessRunningStateChanged | eEventProcessStoppedStateChanged | eEventStdioAvailable, true)...", __FUNCTION__); + nub_event_t pid_status_event = DNBProcessWaitForEvents (pid, eEventProcessRunningStateChanged | eEventProcessStoppedStateChanged | eEventStdioAvailable, true, NULL); + DNBLogThreadedIf(LOG_RNB_PROC, "RNBContext::%s calling DNBProcessWaitForEvent(pid, eEventProcessRunningStateChanged | eEventProcessStoppedStateChanged | eEventStdioAvailable, true) => 0x%8.8x", __FUNCTION__, pid_status_event); + + if (pid_status_event == 0) + { + DNBLogThreadedIf(LOG_RNB_PROC, "RNBContext::%s (pid=%4.4x) got ZERO back from DNBProcessWaitForEvent....", __FUNCTION__, pid); + // done = true; + } + else + { + if (pid_status_event & eEventStdioAvailable) + { + DNBLogThreadedIf(LOG_RNB_PROC, "RNBContext::%s (pid=%4.4x) got stdio available event....", __FUNCTION__, pid); + ctx.Events().SetEvents (RNBContext::event_proc_stdio_available); + // Wait for the main thread to consume this notification if it requested we wait for it + ctx.Events().WaitForResetAck(RNBContext::event_proc_stdio_available); + } + + + if (pid_status_event & (eEventProcessRunningStateChanged | eEventProcessStoppedStateChanged)) + { + nub_state_t pid_state = DNBProcessGetState(pid); + DNBLogThreadedIf(LOG_RNB_PROC, "RNBContext::%s (pid=%4.4x) got process state change: %s", __FUNCTION__, pid, DNBStateAsString(pid_state)); + + // Let the main thread know there is a process state change to see + ctx.Events().SetEvents (RNBContext::event_proc_state_changed); + // Wait for the main thread to consume this notification if it requested we wait for it + ctx.Events().WaitForResetAck(RNBContext::event_proc_state_changed); + + switch (pid_state) + { + case eStateStopped: + break; + + case eStateInvalid: + case eStateExited: + done = true; + break; + } + } + + // Reset any events that we consumed. + DNBProcessResetEvents(pid, pid_status_event); + + } + } + DNBLogThreadedIf(LOG_RNB_PROC, "RNBContext::%s (arg=%p, pid=%4.4x): thread exiting...", __FUNCTION__, arg, pid); + ctx.Events().ResetEvents(event_proc_thread_running); + ctx.Events().SetEvents(event_proc_thread_exiting); + return NULL; +} + + +const char* +RNBContext::EventsAsString (nub_event_t events, std::string& s) +{ + s.clear(); + if (events & event_proc_state_changed) + s += "proc_state_changed "; + if (events & event_proc_thread_running) + s += "proc_thread_running "; + if (events & event_proc_thread_exiting) + s += "proc_thread_exiting "; + if (events & event_proc_stdio_available) + s += "proc_stdio_available "; + if (events & event_read_packet_available) + s += "read_packet_available "; + if (events & event_read_thread_running) + s += "read_thread_running "; + if (events & event_read_thread_running) + s += "read_thread_running "; + return s.c_str(); +} + +const char * +RNBContext::LaunchStatusAsString (std::string& s) +{ + s.clear(); + + const char *err_str = m_launch_status.AsString(); + if (err_str) + s = err_str; + else + { + char error_num_str[64]; + snprintf(error_num_str, sizeof(error_num_str), "%u", m_launch_status.Error()); + s = error_num_str; + } + return s.c_str(); +} + +bool +RNBContext::ProcessStateRunning() const +{ + nub_state_t pid_state = DNBProcessGetState(m_pid); + return pid_state == eStateRunning || pid_state == eStateStepping; +} diff --git a/lldb/tools/debugserver/source/RNBContext.h b/lldb/tools/debugserver/source/RNBContext.h new file mode 100644 index 00000000000..4b5f5ae2cc9 --- /dev/null +++ b/lldb/tools/debugserver/source/RNBContext.h @@ -0,0 +1,123 @@ +//===-- RNBContext.h --------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 12/12/07. +// +//===----------------------------------------------------------------------===// + +#ifndef __RNBContext_h__ +#define __RNBContext_h__ + +#include "RNBDefs.h" +#include "DNBError.h" +#include "PThreadEvent.h" +#include <vector> +#include <string> + +class RNBContext +{ +public: + enum + { + event_proc_state_changed = 0x01, + event_proc_thread_running = 0x02, // Sticky + event_proc_thread_exiting = 0x04, + event_proc_stdio_available = 0x08, + event_read_packet_available = 0x10, + event_read_thread_running = 0x20, // Sticky + event_read_thread_exiting = 0x40, + + normal_event_bits = event_proc_state_changed | + event_proc_thread_exiting | + event_proc_stdio_available | + event_read_packet_available | + event_read_thread_exiting, + + sticky_event_bits = event_proc_thread_running | + event_read_thread_running, + + + all_event_bits = sticky_event_bits | normal_event_bits + } event_t; + //------------------------------------------------------------------ + // Constructors and Destructors + //------------------------------------------------------------------ + RNBContext () : + m_pid(INVALID_NUB_PROCESS), + m_pid_stop_count(0), + m_events(0, all_event_bits), + m_pid_pthread(), + m_launch_status(), + m_arg_vec (), + m_env_vec () + { + } + + virtual ~RNBContext(); + + + nub_process_t ProcessID() const { return m_pid; } + bool HasValidProcessID() const { return m_pid != INVALID_NUB_PROCESS; } + void SetProcessID (nub_process_t pid); + nub_size_t GetProcessStopCount () const { return m_pid_stop_count; } + bool SetProcessStopCount (nub_size_t count) + { + // Returns true if this class' notion of the PID state changed + if (m_pid_stop_count == count) + return false; // Didn't change + m_pid_stop_count = count; + return true; // The stop count has changed. + } + + bool ProcessStateRunning() const; + PThreadEvent& Events( ) { return m_events; } + nub_event_t AllEventBits() const { return all_event_bits; } + nub_event_t NormalEventBits() const { return normal_event_bits; } + nub_event_t StickyEventBits() const { return sticky_event_bits; } + const char* EventsAsString (nub_event_t events, std::string& s); + + int ArgumentCount () const { return m_arg_vec.size(); } + const char * ArgumentAtIndex (int index); + void PushArgument (const char *arg) { if (arg) m_arg_vec.push_back (arg); } + void ClearArgv () { m_arg_vec.erase (m_arg_vec.begin(), m_arg_vec.end()); } + + int EnvironmentCount () const { return m_env_vec.size(); } + const char * EnvironmentAtIndex (int index); + void PushEnvironment (const char *arg) { if (arg) m_env_vec.push_back (arg); } + void ClearEnvironment () { m_env_vec.erase (m_env_vec.begin(), m_env_vec.end()); } + DNBError& LaunchStatus () { return m_launch_status; } + const char * LaunchStatusAsString (std::string& s); + nub_launch_flavor_t LaunchFlavor () const { return m_launch_flavor; } + void SetLaunchFlavor (nub_launch_flavor_t flavor) { m_launch_flavor = flavor; } +protected: + //------------------------------------------------------------------ + // Classes that inherit from RNBContext can see and modify these + //------------------------------------------------------------------ + nub_process_t m_pid; + nub_size_t m_pid_stop_count; + PThreadEvent m_events; // Threaded events that we can wait for + pthread_t m_pid_pthread; + nub_launch_flavor_t m_launch_flavor; // How to launch our inferior process + DNBError m_launch_status; // This holds the status from the last launch attempt. + std::vector<std::string> m_arg_vec; + std::vector<std::string> m_env_vec; // This will be unparsed - entries FOO=value + + void StartProcessStatusThread(); + void StopProcessStatusThread(); + static void* ThreadFunctionProcessStatus(void *arg); + +private: + //------------------------------------------------------------------ + // Outlaw copy and assignment operators + //------------------------------------------------------------------ + RNBContext(const RNBContext& rhs); + RNBContext& operator=(const RNBContext& rhs); +}; + +#endif // #ifndef __RNBContext_h__ diff --git a/lldb/tools/debugserver/source/RNBDefs.h b/lldb/tools/debugserver/source/RNBDefs.h new file mode 100644 index 00000000000..f603d3d22a6 --- /dev/null +++ b/lldb/tools/debugserver/source/RNBDefs.h @@ -0,0 +1,78 @@ +//===-- RNBDefs.h -----------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 12/14/07. +// +//===----------------------------------------------------------------------===// + +#ifndef __RNBDefs_h__ +#define __RNBDefs_h__ + +#include "DNBDefs.h" + +#include <tr1/memory> // for std::tr1::shared_ptr + +extern "C" const unsigned char debugserverVersionString[]; +extern "C" const double debugserverVersionNumber; +#define DEBUGSERVER_PROGRAM_NAME "debugserver" +#define DEBUGSERVER_VERSION_STR debugserverVersionString +#define DEBUGSERVER_VERSION_NUM debugserverVersionNumber + +#if defined (__i386__) + +#define RNB_ARCH "i386" + +#elif defined (__x86_64__) + +#define RNB_ARCH "x86_64" + +#elif defined (__ppc64__) + +#define RNB_ARCH "ppc64" + +#elif defined (__powerpc__) || defined (__ppc__) + +#define RNB_ARCH "ppc" + +#elif defined (__arm__) + +#define RNB_ARCH "armv6" + +#else + +#error undefined architecture + +#endif + +class RNBRemote; +typedef std::tr1::shared_ptr<RNBRemote> RNBRemoteSP; + +typedef enum +{ + rnb_success = 0, + rnb_err = 1, + rnb_not_connected = 2 +} rnb_err_t; + +// Log bits +// reserve low bits for DNB +#define LOG_RNB_MINIMAL ((LOG_LO_USER) << 0) // Minimal logging (min verbosity) +#define LOG_RNB_MEDIUM ((LOG_LO_USER) << 1) // Medium logging (med verbosity) +#define LOG_RNB_MAX ((LOG_LO_USER) << 2) // Max logging (max verbosity) +#define LOG_RNB_COMM ((LOG_LO_USER) << 3) // Log communications (RNBSocket) +#define LOG_RNB_REMOTE ((LOG_LO_USER) << 4) // Log remote (RNBRemote) +#define LOG_RNB_EVENTS ((LOG_LO_USER) << 5) // Log events (PThreadEvents) +#define LOG_RNB_PROC ((LOG_LO_USER) << 6) // Log process state (Process thread) +#define LOG_RNB_PACKETS ((LOG_LO_USER) << 7) // Log gdb remote packets +#define LOG_RNB_ALL (~((LOG_LO_USER) - 1)) +#define LOG_RNB_DEFAULT (LOG_RNB_ALL) + +extern RNBRemoteSP g_remoteSP; + +#endif // #ifndef __RNBDefs_h__ diff --git a/lldb/tools/debugserver/source/RNBRemote.cpp b/lldb/tools/debugserver/source/RNBRemote.cpp new file mode 100644 index 00000000000..3ac3ee9178f --- /dev/null +++ b/lldb/tools/debugserver/source/RNBRemote.cpp @@ -0,0 +1,3187 @@ +//===-- RNBRemote.cpp -------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 12/12/07. +// +//===----------------------------------------------------------------------===// + +#include "RNBRemote.h" + +#include <errno.h> +#include <unistd.h> +#include <signal.h> +#include <mach/exception_types.h> +#include <sys/sysctl.h> + +#include "DNB.h" +#include "DNBLog.h" +#include "DNBThreadResumeActions.h" +#include "RNBContext.h" +#include "RNBServices.h" +#include "RNBSocket.h" +#include "StringExtractor.h" + +#include <iomanip> +#include <sstream> + +#include <TargetConditionals.h> // for endianness predefines + +//---------------------------------------------------------------------- +// std::iostream formatting macros +//---------------------------------------------------------------------- +#define RAW_HEXBASE std::setfill('0') << std::hex << std::right +#define HEXBASE '0' << 'x' << RAW_HEXBASE +#define RAWHEX8(x) RAW_HEXBASE << std::setw(2) << ((uint32_t)((uint8_t)x)) +#define RAWHEX16 RAW_HEXBASE << std::setw(4) +#define RAWHEX32 RAW_HEXBASE << std::setw(8) +#define RAWHEX64 RAW_HEXBASE << std::setw(16) +#define HEX8(x) HEXBASE << std::setw(2) << ((uint32_t)(x)) +#define HEX16 HEXBASE << std::setw(4) +#define HEX32 HEXBASE << std::setw(8) +#define HEX64 HEXBASE << std::setw(16) +#define RAW_HEX(x) RAW_HEXBASE << std::setw(sizeof(x)*2) << (x) +#define HEX(x) HEXBASE << std::setw(sizeof(x)*2) << (x) +#define RAWHEX_SIZE(x, sz) RAW_HEXBASE << std::setw((sz)) << (x) +#define HEX_SIZE(x, sz) HEXBASE << std::setw((sz)) << (x) +#define STRING_WIDTH(w) std::setfill(' ') << std::setw(w) +#define LEFT_STRING_WIDTH(s, w) std::left << std::setfill(' ') << std::setw(w) << (s) << std::right +#define DECIMAL std::dec << std::setfill(' ') +#define DECIMAL_WIDTH(w) DECIMAL << std::setw(w) +#define FLOAT(n, d) std::setfill(' ') << std::setw((n)+(d)+1) << std::setprecision(d) << std::showpoint << std::fixed +#define INDENT_WITH_SPACES(iword_idx) std::setfill(' ') << std::setw((iword_idx)) << "" +#define INDENT_WITH_TABS(iword_idx) std::setfill('\t') << std::setw((iword_idx)) << "" +// Class to handle communications via gdb remote protocol. + +extern void ASLLogCallback(void *baton, uint32_t flags, const char *format, va_list args); + +RNBRemote::RNBRemote (bool use_native_regs) : + m_ctx(), + m_comm(), + m_extended_mode(false), + m_noack_mode(false), + m_continue_thread(-1), + m_thread(-1), + m_mutex(), + m_packets_recvd(0), + m_packets(), + m_rx_packets(), + m_rx_partial_data(), + m_rx_pthread(0), + m_breakpoints(), + m_max_payload_size(DEFAULT_GDB_REMOTE_PROTOCOL_BUFSIZE - 4), + m_use_native_regs (use_native_regs) +{ + DNBLogThreadedIf (LOG_RNB_REMOTE, "%s", __PRETTY_FUNCTION__); + CreatePacketTable (); +} + + +RNBRemote::~RNBRemote() +{ + DNBLogThreadedIf (LOG_RNB_REMOTE, "%s", __PRETTY_FUNCTION__); + StopReadRemoteDataThread(); +} + +void +RNBRemote::CreatePacketTable () +{ + // Step required to add new packets: + // 1 - Add new enumeration to RNBRemote::PacketEnum + // 2 - Create a the RNBRemote::HandlePacket_ function if a new function is needed + // 3 - Register the Packet definition with any needed callbacks in this fucntion + // - If no response is needed for a command, then use NULL for the normal callback + // - If the packet is not supported while the target is running, use NULL for the async callback + // 4 - If the packet is a standard packet (starts with a '$' character + // followed by the payload and then '#' and checksum, then you are done + // else go on to step 5 + // 5 - if the packet is a fixed length packet: + // - modify the switch statement for the first character in the payload + // in RNBRemote::CommDataReceived so it doesn't reject the new packet + // type as invalid + // - modify the switch statement for the first character in the payload + // in RNBRemote::GetPacketPayload and make sure the payload of the packet + // is returned correctly + + std::vector <Packet> &t = m_packets; + t.push_back (Packet (ack, NULL, NULL, "+", "ACK")); + t.push_back (Packet (nack, NULL, NULL, "-", "!ACK")); + t.push_back (Packet (read_memory, &RNBRemote::HandlePacket_m, NULL, "m", "Read memory")); + t.push_back (Packet (read_register, &RNBRemote::HandlePacket_p, NULL, "p", "Read one register")); + t.push_back (Packet (read_general_regs, &RNBRemote::HandlePacket_g, NULL, "g", "Read registers")); + t.push_back (Packet (write_memory, &RNBRemote::HandlePacket_M, NULL, "M", "Write memory")); + t.push_back (Packet (write_register, &RNBRemote::HandlePacket_P, NULL, "P", "Write one register")); + t.push_back (Packet (write_general_regs, &RNBRemote::HandlePacket_G, NULL, "G", "Write registers")); + t.push_back (Packet (insert_mem_bp, &RNBRemote::HandlePacket_z, NULL, "Z0", "Insert memory breakpoint")); + t.push_back (Packet (remove_mem_bp, &RNBRemote::HandlePacket_z, NULL, "z0", "Remove memory breakpoint")); + t.push_back (Packet (single_step, &RNBRemote::HandlePacket_s, NULL, "s", "Single step")); + t.push_back (Packet (cont, &RNBRemote::HandlePacket_c, NULL, "c", "continue")); + t.push_back (Packet (single_step_with_sig, &RNBRemote::HandlePacket_S, NULL, "S", "Single step with signal")); + t.push_back (Packet (set_thread, &RNBRemote::HandlePacket_H, NULL, "H", "Set thread")); + t.push_back (Packet (halt, &RNBRemote::HandlePacket_last_signal, &RNBRemote::HandlePacket_stop_process, "\x03", "^C")); +// t.push_back (Packet (use_extended_mode, &RNBRemote::HandlePacket_UNIMPLEMENTED, NULL, "!", "Use extended mode")); + t.push_back (Packet (why_halted, &RNBRemote::HandlePacket_last_signal, NULL, "?", "Why did target halt")); + t.push_back (Packet (set_argv, &RNBRemote::HandlePacket_A, NULL, "A", "Set argv")); +// t.push_back (Packet (set_bp, &RNBRemote::HandlePacket_UNIMPLEMENTED, NULL, "B", "Set/clear breakpoint")); + t.push_back (Packet (continue_with_sig, &RNBRemote::HandlePacket_C, NULL, "C", "Continue with signal")); + t.push_back (Packet (detach, &RNBRemote::HandlePacket_D, NULL, "D", "Detach gdb from remote system")); +// t.push_back (Packet (step_inferior_one_cycle, &RNBRemote::HandlePacket_UNIMPLEMENTED, NULL, "i", "Step inferior by one clock cycle")); +// t.push_back (Packet (signal_and_step_inf_one_cycle, &RNBRemote::HandlePacket_UNIMPLEMENTED, NULL, "I", "Signal inferior, then step one clock cyle")); + t.push_back (Packet (kill, &RNBRemote::HandlePacket_k, NULL, "k", "Kill")); +// t.push_back (Packet (restart, &RNBRemote::HandlePacket_UNIMPLEMENTED, NULL, "R", "Restart inferior")); +// t.push_back (Packet (search_mem_backwards, &RNBRemote::HandlePacket_UNIMPLEMENTED, NULL, "t", "Search memory backwards")); + t.push_back (Packet (thread_alive_p, &RNBRemote::HandlePacket_T, NULL, "T", "Is thread alive")); + t.push_back (Packet (vattach, &RNBRemote::HandlePacket_v, NULL, "vAttach", "Attach to a new process")); + t.push_back (Packet (vattachwait, &RNBRemote::HandlePacket_v, NULL, "vAttachWait", "Wait for a process to start up then attach to it")); + t.push_back (Packet (vcont_list_actions, &RNBRemote::HandlePacket_v, NULL, "vCont;", "Verbose resume with thread actions")); + t.push_back (Packet (vcont_list_actions, &RNBRemote::HandlePacket_v, NULL, "vCont?", "List valid continue-with-thread-actions actions")); + // The X packet doesn't currently work. If/when it does, remove the line above and uncomment out the line below +// t.push_back (Packet (write_data_to_memory, &RNBRemote::HandlePacket_X, NULL, "X", "Write data to memory")); +// t.push_back (Packet (insert_hardware_bp, &RNBRemote::HandlePacket_UNIMPLEMENTED, NULL, "Z1", "Insert hardware breakpoint")); +// t.push_back (Packet (remove_hardware_bp, &RNBRemote::HandlePacket_UNIMPLEMENTED, NULL, "z1", "Remove hardware breakpoint")); +// t.push_back (Packet (insert_write_watch_bp, &RNBRemote::HandlePacket_UNIMPLEMENTED, NULL, "Z2", "Insert write watchpoint")); +// t.push_back (Packet (remove_write_watch_bp, &RNBRemote::HandlePacket_UNIMPLEMENTED, NULL, "z2", "Remove write watchpoint")); +// t.push_back (Packet (insert_read_watch_bp, &RNBRemote::HandlePacket_UNIMPLEMENTED, NULL, "Z3", "Insert read watchpoint")); +// t.push_back (Packet (remove_read_watch_bp, &RNBRemote::HandlePacket_UNIMPLEMENTED, NULL, "z3", "Remove read watchpoint")); +// t.push_back (Packet (insert_access_watch_bp, &RNBRemote::HandlePacket_UNIMPLEMENTED, NULL, "Z4", "Insert access watchpoint")); +// t.push_back (Packet (remove_access_watch_bp, &RNBRemote::HandlePacket_UNIMPLEMENTED, NULL, "z4", "Remove access watchpoint")); + t.push_back (Packet (query_current_thread_id, &RNBRemote::HandlePacket_qC, NULL, "qC", "Query current thread ID")); +// t.push_back (Packet (query_memory_crc, &RNBRemote::HandlePacket_UNIMPLEMENTED, NULL, "qCRC:", "Compute CRC of memory region")); + t.push_back (Packet (query_thread_ids_first, &RNBRemote::HandlePacket_qThreadInfo, NULL, "qfThreadInfo", "Get list of active threads (first req)")); + t.push_back (Packet (query_thread_ids_subsequent, &RNBRemote::HandlePacket_qThreadInfo, NULL, "qsThreadInfo", "Get list of active threads (subsequent req)")); + // APPLE LOCAL: qThreadStopInfo + // syntax: qThreadStopInfoTTTT + // TTTT is hex thread ID + t.push_back (Packet (query_thread_stop_info, &RNBRemote::HandlePacket_qThreadStopInfo, NULL, "qThreadStopInfo", "Get detailed info on why the specified thread stopped")); + t.push_back (Packet (query_thread_extra_info, &RNBRemote::HandlePacket_qThreadExtraInfo,NULL, "qThreadExtraInfo", "Get printable status of a thread")); +// t.push_back (Packet (query_image_offsets, &RNBRemote::HandlePacket_UNIMPLEMENTED, NULL, "qOffsets", "Report offset of loaded program")); + t.push_back (Packet (query_launch_success, &RNBRemote::HandlePacket_qLaunchSuccess,NULL, "qLaunchSuccess", "Report the success or failure of the launch attempt")); + t.push_back (Packet (query_register_info, &RNBRemote::HandlePacket_qRegisterInfo, NULL, "qRegisterInfo", "Dynamically discover remote register context information.")); + t.push_back (Packet (query_shlib_notify_info_addr, &RNBRemote::HandlePacket_qShlibInfoAddr,NULL, "qShlibInfoAddr", "Returns the address that contains info needed for getting shared library notifications")); + t.push_back (Packet (query_step_packet_supported, &RNBRemote::HandlePacket_qStepPacketSupported,NULL, "qStepPacketSupported", "Replys with OK if the 's' packet is supported.")); + t.push_back (Packet (query_host_info, &RNBRemote::HandlePacket_qHostInfo, NULL, "qHostInfo", "Replies with multiple 'key:value;' tuples appended to each other.")); +// t.push_back (Packet (query_symbol_lookup, &RNBRemote::HandlePacket_UNIMPLEMENTED, NULL, "qSymbol", "Notify that host debugger is ready to do symbol lookups")); + t.push_back (Packet (start_noack_mode, &RNBRemote::HandlePacket_Q , NULL, "QStartNoAckMode", "Request that " DEBUGSERVER_PROGRAM_NAME " stop acking remote protocol packets")); + t.push_back (Packet (set_logging_mode, &RNBRemote::HandlePacket_Q , NULL, "QSetLogging:", "Request that the " DEBUGSERVER_PROGRAM_NAME " set its logging mode bits")); + t.push_back (Packet (set_max_packet_size, &RNBRemote::HandlePacket_Q , NULL, "QSetMaxPacketSize:", "Tell " DEBUGSERVER_PROGRAM_NAME " the max sized packet gdb can handle")); + t.push_back (Packet (set_max_payload_size, &RNBRemote::HandlePacket_Q , NULL, "QSetMaxPayloadSize:", "Tell " DEBUGSERVER_PROGRAM_NAME " the max sized payload gdb can handle")); + t.push_back (Packet (set_environment_variable, &RNBRemote::HandlePacket_Q , NULL, "QEnvironment:", "Add an environment variable to the inferior's environment")); +// t.push_back (Packet (pass_signals_to_inferior, &RNBRemote::HandlePacket_UNIMPLEMENTED, NULL, "QPassSignals:", "Specify which signals are passed to the inferior")); + t.push_back (Packet (allocate_memory, &RNBRemote::HandlePacket_AllocateMemory, NULL, "_M", "Allocate memory in the inferior process.")); + t.push_back (Packet (deallocate_memory, &RNBRemote::HandlePacket_DeallocateMemory, NULL, "_m", "Deallocate memory in the inferior process.")); +} + + +void +RNBRemote::FlushSTDIO () +{ + if (m_ctx.HasValidProcessID()) + { + nub_process_t pid = m_ctx.ProcessID(); + char buf[256]; + nub_size_t count; + do + { + count = DNBProcessGetAvailableSTDOUT(pid, buf, sizeof(buf)); + if (count > 0) + { + SendSTDOUTPacket (buf, count); + } + } while (count > 0); + + do + { + count = DNBProcessGetAvailableSTDERR(pid, buf, sizeof(buf)); + if (count > 0) + { + SendSTDERRPacket (buf, count); + } + } while (count > 0); + } +} + +rnb_err_t +RNBRemote::SendHexEncodedBytePacket (const char *header, const void *buf, size_t buf_len, const char *footer) +{ + std::ostringstream packet_sstrm; + // Append the header cstr if there was one + if (header && header[0]) + packet_sstrm << header; + nub_size_t i; + const uint8_t *ubuf8 = (const uint8_t *)buf; + for (i=0; i<buf_len; i++) + { + packet_sstrm << RAWHEX8(ubuf8[i]); + } + // Append the footer cstr if there was one + if (footer && footer[0]) + packet_sstrm << footer; + + return SendPacket(packet_sstrm.str()); +} + +rnb_err_t +RNBRemote::SendSTDOUTPacket (char *buf, nub_size_t buf_size) +{ + if (buf_size == 0) + return rnb_success; + return SendHexEncodedBytePacket("O", buf, buf_size, NULL); +} + +rnb_err_t +RNBRemote::SendSTDERRPacket (char *buf, nub_size_t buf_size) +{ + if (buf_size == 0) + return rnb_success; + return SendHexEncodedBytePacket("O", buf, buf_size, NULL); +} + +rnb_err_t +RNBRemote::SendPacket (const std::string &s) +{ + DNBLogThreadedIf (LOG_RNB_MAX, "%8d RNBRemote::%s (%s) called", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__, s.c_str()); + std::string sendpacket = "$" + s + "#"; + int cksum = 0; + char hexbuf[5]; + + if (m_noack_mode) + { + sendpacket += "00"; + } + else + { + for (int i = 0; i != s.size(); ++i) + cksum += s[i]; + snprintf (hexbuf, sizeof hexbuf, "%02x", cksum & 0xff); + sendpacket += hexbuf; + } + + rnb_err_t err = m_comm.Write (sendpacket.c_str(), sendpacket.size()); + if (err != rnb_success) + return err; + + if (m_noack_mode) + return rnb_success; + + std::string reply; + RNBRemote::Packet packet; + err = GetPacket (reply, packet, true); + + if (err != rnb_success) + { + DNBLogThreadedIf (LOG_RNB_REMOTE, "%8d RNBRemote::%s (%s) got error trying to get reply...", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__, sendpacket.c_str()); + return err; + } + + DNBLogThreadedIf (LOG_RNB_MAX, "%8d RNBRemote::%s (%s) got reply: '%s'", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__, sendpacket.c_str(), reply.c_str()); + + if (packet.type == ack) + return rnb_success; + + // Should we try to resend the packet at this layer? + // if (packet.command == nack) + return rnb_err; +} + +/* Get a packet via gdb remote protocol. + Strip off the prefix/suffix, verify the checksum to make sure + a valid packet was received, send an ACK if they match. */ + +rnb_err_t +RNBRemote::GetPacketPayload (std::string &return_packet) +{ + //DNBLogThreadedIf (LOG_RNB_MAX, "%8u RNBRemote::%s called", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__); + + PThreadMutex::Locker locker(m_mutex); + if (m_rx_packets.empty()) + { + // Only reset the remote command available event if we have no more packets + m_ctx.Events().ResetEvents ( RNBContext::event_read_packet_available ); + //DNBLogThreadedIf (LOG_RNB_MAX, "%8u RNBRemote::%s error: no packets available...", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__); + return rnb_err; + } + + //DNBLogThreadedIf (LOG_RNB_MAX, "%8u RNBRemote::%s has %u queued packets", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__, m_rx_packets.size()); + return_packet.swap(m_rx_packets.front()); + m_rx_packets.pop_front(); + locker.Reset(); // Release our lock on the mutex + + if (m_rx_packets.empty()) + { + // Reset the remote command available event if we have no more packets + m_ctx.Events().ResetEvents ( RNBContext::event_read_packet_available ); + } + + //DNBLogThreadedIf (LOG_RNB_MEDIUM, "%8u RNBRemote::%s: '%s'", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__, return_packet.c_str()); + + switch (return_packet[0]) + { + case '+': + case '-': + case '\x03': + break; + + case '$': + { + int packet_checksum = 0; + if (!m_noack_mode) + { + for (int i = return_packet.size() - 2; i < return_packet.size(); ++i) + { + char checksum_char = tolower (return_packet[i]); + if (!isxdigit (checksum_char)) + { + m_comm.Write ("-", 1); + DNBLogThreadedIf (LOG_RNB_REMOTE, "%8u RNBRemote::%s error: packet with invalid checksum characters: %s", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__, return_packet.c_str()); + return rnb_err; + } + } + packet_checksum = strtol (&return_packet[return_packet.size() - 2], NULL, 16); + } + + return_packet.erase(0,1); // Strip the leading '$' + return_packet.erase(return_packet.size() - 3);// Strip the #XX checksum + + if (!m_noack_mode) + { + // Compute the checksum + int computed_checksum = 0; + for (std::string::iterator it = return_packet.begin (); + it != return_packet.end (); + ++it) + { + computed_checksum += *it; + } + + if (packet_checksum == (computed_checksum & 0xff)) + { + //DNBLogThreadedIf (LOG_RNB_MEDIUM, "%8u RNBRemote::%s sending ACK for '%s'", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__, return_packet.c_str()); + m_comm.Write ("+", 1); + } + else + { + DNBLogThreadedIf (LOG_RNB_MEDIUM, "%8u RNBRemote::%s sending ACK for '%s' (error: packet checksum mismatch (0x%2.2x != 0x%2.2x))", + (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), + __FUNCTION__, + return_packet.c_str(), + packet_checksum, + computed_checksum); + m_comm.Write ("-", 1); + return rnb_err; + } + } + } + break; + + default: + DNBLogThreadedIf (LOG_RNB_REMOTE, "%8u RNBRemote::%s tossing unexpected packet???? %s", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__, return_packet.c_str()); + if (!m_noack_mode) + m_comm.Write ("-", 1); + return rnb_err; + } + + return rnb_success; +} + +rnb_err_t +RNBRemote::HandlePacket_UNIMPLEMENTED (const char* p) +{ + DNBLogThreadedIf (LOG_RNB_MAX, "%8u RNBRemote::%s(\"%s\")", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__, p ? p : "NULL"); + return SendPacket (""); +} + +rnb_err_t +RNBRemote::HandlePacket_ILLFORMED (const char *description) +{ + DNBLogThreadedIf (LOG_RNB_MAX, "%8u RNBRemote::%s sending ILLFORMED", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__); + return SendPacket ("E03"); +} + +rnb_err_t +RNBRemote::GetPacket (std::string &packet_payload, RNBRemote::Packet& packet_info, bool wait) +{ + std::string payload; + rnb_err_t err = GetPacketPayload (payload); + if (err != rnb_success) + { + PThreadEvent& events = m_ctx.Events(); + nub_event_t set_events = events.GetEventBits(); + // TODO: add timeout version of GetPacket?? We would then need to pass + // that timeout value along to DNBProcessTimedWaitForEvent. + if (!wait || ((set_events & RNBContext::event_read_thread_running) == 0)) + return err; + + const nub_event_t events_to_wait_for = RNBContext::event_read_packet_available | RNBContext::event_read_thread_exiting; + set_events = 0; + + while ((set_events = events.WaitForSetEvents(events_to_wait_for)) != 0) + { + if (set_events & RNBContext::event_read_packet_available) + { + // Try the queue again now that we got an event + err = GetPacketPayload (payload); + if (err == rnb_success) + break; + } + + if (set_events & RNBContext::event_read_thread_exiting) + err = rnb_not_connected; + + if (err == rnb_not_connected) + return err; + + } while (err == rnb_err); + + if (set_events == 0) + err = rnb_not_connected; + } + + if (err == rnb_success) + { + Packet::iterator it; + for (it = m_packets.begin (); it != m_packets.end (); ++it) + { + if (payload.compare (0, it->abbrev.size(), it->abbrev) == 0) + break; + } + + // A packet we don't have an entry for. This can happen when we + // get a packet that we don't know about or support. We just reply + // accordingly and go on. + if (it == m_packets.end ()) + { + DNBLogThreadedIf (LOG_RNB_PACKETS, "unimplemented packet: '%s'", payload.c_str()); + HandlePacket_UNIMPLEMENTED(payload.c_str()); + return rnb_err; + } + else + { + packet_info = *it; + packet_payload = payload; + } + } + return err; +} + +rnb_err_t +RNBRemote::HandleAsyncPacket(PacketEnum *type) +{ + DNBLogThreadedIf (LOG_RNB_REMOTE, "%8u RNBRemote::%s", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__); + static DNBTimer g_packetTimer(true); + rnb_err_t err = rnb_err; + std::string packet_data; + RNBRemote::Packet packet_info; + err = GetPacket (packet_data, packet_info, false); + + if (err == rnb_success) + { + if (!packet_data.empty() && isprint(packet_data[0])) + DNBLogThreadedIf (LOG_RNB_REMOTE | LOG_RNB_PACKETS, "HandleAsyncPacket (\"%s\");", packet_data.c_str()); + else + DNBLogThreadedIf (LOG_RNB_REMOTE | LOG_RNB_PACKETS, "HandleAsyncPacket (%s);", packet_info.printable_name.c_str()); + + HandlePacketCallback packet_callback = packet_info.async; + if (packet_callback != NULL) + { + if (type != NULL) + *type = packet_info.type; + return (this->*packet_callback)(packet_data.c_str()); + } + } + + return err; +} + +rnb_err_t +RNBRemote::HandleReceivedPacket(PacketEnum *type) +{ + static DNBTimer g_packetTimer(true); + + // DNBLogThreadedIf (LOG_RNB_REMOTE, "%8u RNBRemote::%s", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__); + rnb_err_t err = rnb_err; + std::string packet_data; + RNBRemote::Packet packet_info; + err = GetPacket (packet_data, packet_info, false); + + if (err == rnb_success) + { + DNBLogThreadedIf (LOG_RNB_REMOTE, "HandleReceivedPacket (\"%s\");", packet_data.c_str()); + HandlePacketCallback packet_callback = packet_info.normal; + if (packet_callback != NULL) + { + if (type != NULL) + *type = packet_info.type; + return (this->*packet_callback)(packet_data.c_str()); + } + else + { + // Do not fall through to end of this function, if we have valid + // packet_info and it has a NULL callback, then we need to respect + // that it may not want any response or anything to be done. + return err; + } + } + return rnb_err; +} + +void +RNBRemote::CommDataReceived(const std::string& new_data) +{ + // DNBLogThreadedIf (LOG_RNB_REMOTE, "%8d RNBRemote::%s called", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__); + { + // Put the packet data into the buffer in a thread safe fashion + PThreadMutex::Locker locker(m_mutex); + + std::string data; + // See if we have any left over data from a previous call to this + // function? + if (!m_rx_partial_data.empty()) + { + // We do, so lets start with that data + data.swap(m_rx_partial_data); + } + // Append the new incoming data + data += new_data; + + // Parse up the packets into gdb remote packets + uint32_t idx = 0; + const size_t data_size = data.size(); + + while (idx < data_size) + { + // end_idx must be one past the last valid packet byte. Start + // it off with an invalid value that is the same as the current + // index. + size_t end_idx = idx; + + switch (data[idx]) + { + case '+': // Look for ack + case '-': // Look for cancel + case '\x03': // ^C to halt target + end_idx = idx + 1; // The command is one byte long... + break; + + case '$': + // Look for a standard gdb packet? + end_idx = data.find('#', idx + 1); + if (end_idx == std::string::npos || end_idx + 2 > data_size) + { + end_idx = std::string::npos; + } + else + { + // Add two for the checksum bytes + end_idx += 4; + } + break; + + default: + break; + } + + if (end_idx == std::string::npos) + { + // Not all data may be here for the packet yet, save it for + // next time through this function. + m_rx_partial_data += data.substr(idx); + //DNBLogThreadedIf (LOG_RNB_MAX, "%8d RNBRemote::%s saving data for later[%u, npos): '%s'",(uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__, idx, m_rx_partial_data.c_str()); + idx = end_idx; + } + else + if (idx < end_idx) + { + m_packets_recvd++; + // Hack to get rid of initial '+' ACK??? + if (m_packets_recvd == 1 && (end_idx == idx + 1) && data[idx] == '+') + { + //DNBLogThreadedIf (LOG_RNB_REMOTE, "%8d RNBRemote::%s throwing first ACK away....[%u, npos): '+'",(uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__, idx); + } + else + { + // We have a valid packet... + m_rx_packets.push_back(data.substr(idx, end_idx - idx)); + DNBLogThreadedIf (LOG_RNB_PACKETS, "getpkt: %s", m_rx_packets.back().c_str()); + } + idx = end_idx; + } + else + { + DNBLogThreadedIf (LOG_RNB_MAX, "%8d RNBRemote::%s tossing junk byte at %c",(uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__, data[idx]); + idx = idx + 1; + } + } + } + + if (!m_rx_packets.empty()) + { + // Let the main thread know we have received a packet + + //DNBLogThreadedIf (LOG_RNB_EVENTS, "%8d RNBRemote::%s called events.SetEvent(RNBContext::event_read_packet_available)", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__); + PThreadEvent& events = m_ctx.Events(); + events.SetEvents (RNBContext::event_read_packet_available); + } +} + +rnb_err_t +RNBRemote::GetCommData () +{ + // DNBLogThreadedIf (LOG_RNB_REMOTE, "%8d RNBRemote::%s called", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__); + std::string comm_data; + rnb_err_t err = m_comm.Read (comm_data); + if (err == rnb_success) + { + if (!comm_data.empty()) + CommDataReceived (comm_data); + } + return err; +} + +void +RNBRemote::StartReadRemoteDataThread() +{ + DNBLogThreadedIf (LOG_RNB_REMOTE, "%8u RNBRemote::%s called", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__); + PThreadEvent& events = m_ctx.Events(); + if ((events.GetEventBits() & RNBContext::event_read_thread_running) == 0) + { + events.ResetEvents (RNBContext::event_read_thread_exiting); + int err = ::pthread_create (&m_rx_pthread, NULL, ThreadFunctionReadRemoteData, this); + if (err == 0) + { + // Our thread was successfully kicked off, wait for it to + // set the started event so we can safely continue + events.WaitForSetEvents (RNBContext::event_read_thread_running); + } + else + { + events.ResetEvents (RNBContext::event_read_thread_running); + events.SetEvents (RNBContext::event_read_thread_exiting); + } + } +} + +void +RNBRemote::StopReadRemoteDataThread() +{ + DNBLogThreadedIf (LOG_RNB_REMOTE, "%8u RNBRemote::%s called", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__); + PThreadEvent& events = m_ctx.Events(); + if ((events.GetEventBits() & RNBContext::event_read_thread_running) == RNBContext::event_read_thread_running) + { + m_comm.Disconnect(true); + struct timespec timeout_abstime; + DNBTimer::OffsetTimeOfDay(&timeout_abstime, 2, 0); + + // Wait for 2 seconds for the remote data thread to exit + if (events.WaitForSetEvents(RNBContext::event_read_thread_exiting, &timeout_abstime) == 0) + { + // Kill the remote data thread??? + } + } +} + + +void* +RNBRemote::ThreadFunctionReadRemoteData(void *arg) +{ + // Keep a shared pointer reference so this doesn't go away on us before the thread is killed. + DNBLogThreadedIf(LOG_RNB_REMOTE, "RNBRemote::%s (%p): thread starting...", __FUNCTION__, arg); + RNBRemoteSP remoteSP(g_remoteSP); + if (remoteSP.get() != NULL) + { + RNBRemote* remote = remoteSP.get(); + PThreadEvent& events = remote->Context().Events(); + events.SetEvents (RNBContext::event_read_thread_running); + // START: main receive remote command thread loop + bool done = false; + while (!done) + { + rnb_err_t err = remote->GetCommData(); + + switch (err) + { + case rnb_success: + break; + + default: + case rnb_err: + DNBLogThreadedIf (LOG_RNB_REMOTE, "RNBSocket::GetCommData returned error %u", err); + done = true; + break; + + case rnb_not_connected: + DNBLogThreadedIf (LOG_RNB_REMOTE, "RNBSocket::GetCommData returned not connected..."); + done = true; + break; + } + } + // START: main receive remote command thread loop + events.ResetEvents (RNBContext::event_read_thread_running); + events.SetEvents (RNBContext::event_read_thread_exiting); + } + DNBLogThreadedIf(LOG_RNB_REMOTE, "RNBRemote::%s (%p): thread exiting...", __FUNCTION__, arg); + return NULL; +} + + + +/* Read the bytes in STR which are GDB Remote Protocol binary encoded bytes + (8-bit bytes). + This encoding uses 0x7d ('}') as an escape character for 0x7d ('}'), + 0x23 ('#'), and 0x24 ('$'). + LEN is the number of bytes to be processed. If a character is escaped, + it is 2 characters for LEN. A LEN of -1 means encode-until-nul-byte + (end of string). */ + +std::vector<uint8_t> +decode_binary_data (const char *str, int len) +{ + std::vector<uint8_t> bytes; + if (len == 0) + { + return bytes; + } + if (len == -1) + len = strlen (str); + + while (len--) + { + unsigned char c = *str; + if (c == 0x7d && len > 0) + { + len--; + str++; + c ^= 0x20; + } + bytes.push_back (c); + } + return bytes; +} + +typedef struct register_map_entry +{ + uint32_t gdb_regnum; // gdb register number + uint32_t gdb_size; // gdb register size in bytes (can be greater than or less than to debugnub size...) + const char * gdb_name; // gdb register name + DNBRegisterInfo nub_info; // debugnub register info + const uint8_t* fail_value; // Value to print if case we fail to reg this register (if this is NULL, we will return an error) + int expedite; // expedite delivery of this register in last stop reply packets +} register_map_entry_t; + + + +// If the notion of registers differs from what is handed out by the +// architecture, then flavors can be defined here. + +static const uint32_t MAX_REGISTER_BYTE_SIZE = 16; +static const uint8_t k_zero_bytes[MAX_REGISTER_BYTE_SIZE] = {0}; +static std::vector<register_map_entry_t> g_dynamic_register_map; +static register_map_entry_t *g_reg_entries = NULL; +static size_t g_num_reg_entries = 0; + +static void +RegisterEntryNotAvailable (register_map_entry_t *reg_entry) +{ + reg_entry->fail_value = k_zero_bytes; + reg_entry->nub_info.set = INVALID_NUB_REGNUM; + reg_entry->nub_info.reg = INVALID_NUB_REGNUM; + reg_entry->nub_info.name = NULL; + reg_entry->nub_info.alt = NULL; + reg_entry->nub_info.type = InvalidRegType; + reg_entry->nub_info.format = InvalidRegFormat; + reg_entry->nub_info.size = 0; + reg_entry->nub_info.offset = 0; + reg_entry->nub_info.reg_gcc = INVALID_NUB_REGNUM; + reg_entry->nub_info.reg_dwarf = INVALID_NUB_REGNUM; + reg_entry->nub_info.reg_generic = INVALID_NUB_REGNUM; + reg_entry->nub_info.reg_gdb = INVALID_NUB_REGNUM; +} + +#if defined (__arm__) + +//---------------------------------------------------------------------- +// ARM regiseter sets as gdb knows them +//---------------------------------------------------------------------- + +register_map_entry_t +g_gdb_register_map_arm[] = +{ + { 0, 4, "r0", {0}, NULL, 1}, + { 1, 4, "r1", {0}, NULL, 1}, + { 2, 4, "r2", {0}, NULL, 1}, + { 3, 4, "r3", {0}, NULL, 1}, + { 4, 4, "r4", {0}, NULL, 1}, + { 5, 4, "r5", {0}, NULL, 1}, + { 6, 4, "r6", {0}, NULL, 1}, + { 7, 4, "r7", {0}, NULL, 1}, + { 8, 4, "r8", {0}, NULL, 1}, + { 9, 4, "r9", {0}, NULL, 1}, + { 10, 4, "r10", {0}, NULL, 1}, + { 11, 4, "r11", {0}, NULL, 1}, + { 12, 4, "r12", {0}, NULL, 1}, + { 13, 4, "sp", {0}, NULL, 1}, + { 14, 4, "lr", {0}, NULL, 1}, + { 15, 4, "pc", {0}, NULL, 1}, + { 16, 12, "f0", NULL, k_zero_bytes, 0}, + { 17, 12, "f1", NULL, k_zero_bytes, 0}, + { 18, 12, "f2", NULL, k_zero_bytes, 0}, + { 19, 12, "f3", NULL, k_zero_bytes, 0}, + { 20, 12, "f4", NULL, k_zero_bytes, 0}, + { 21, 12, "f5", NULL, k_zero_bytes, 0}, + { 22, 12, "f6", NULL, k_zero_bytes, 0}, + { 23, 12, "f7", NULL, k_zero_bytes, 0}, + { 24, 4, "fps", {0}, NULL, 0}, + { 25, 4,"cpsr", {0}, NULL, 1}, + { 26, 4, "s0", {0}, NULL, 0}, + { 27, 4, "s1", {0}, NULL, 0}, + { 28, 4, "s2", {0}, NULL, 0}, + { 29, 4, "s3", {0}, NULL, 0}, + { 30, 4, "s4", {0}, NULL, 0}, + { 31, 4, "s5", {0}, NULL, 0}, + { 32, 4, "s6", {0}, NULL, 0}, + { 33, 4, "s7", {0}, NULL, 0}, + { 34, 4, "s8", {0}, NULL, 0}, + { 35, 4, "s9", {0}, NULL, 0}, + { 36, 4, "s10", {0}, NULL, 0}, + { 37, 4, "s11", {0}, NULL, 0}, + { 38, 4, "s12", {0}, NULL, 0}, + { 39, 4, "s13", {0}, NULL, 0}, + { 40, 4, "s14", {0}, NULL, 0}, + { 41, 4, "s15", {0}, NULL, 0}, + { 42, 4, "s16", {0}, NULL, 0}, + { 43, 4, "s17", {0}, NULL, 0}, + { 44, 4, "s18", {0}, NULL, 0}, + { 45, 4, "s19", {0}, NULL, 0}, + { 46, 4, "s20", {0}, NULL, 0}, + { 47, 4, "s21", {0}, NULL, 0}, + { 48, 4, "s22", {0}, NULL, 0}, + { 49, 4, "s23", {0}, NULL, 0}, + { 50, 4, "s24", {0}, NULL, 0}, + { 51, 4, "s25", {0}, NULL, 0}, + { 52, 4, "s26", {0}, NULL, 0}, + { 53, 4, "s27", {0}, NULL, 0}, + { 54, 4, "s28", {0}, NULL, 0}, + { 55, 4, "s29", {0}, NULL, 0}, + { 56, 4, "s30", {0}, NULL, 0}, + { 57, 4, "s31", {0}, NULL, 0}, + { 58, 4, "fpscr", {0}, NULL, 0} +}; + +void +RNBRemote::InitializeRegisters (int use_native_registers) +{ + if (use_native_registers) + { + RNBRemote::InitializeNativeRegisters(); + } + else + { + const size_t num_regs = sizeof (g_gdb_register_map_arm) / sizeof (register_map_entry_t); + for (uint32_t i=0; i<num_regs; ++i) + { + if (!DNBGetRegisterInfoByName (g_gdb_register_map_arm[i].gdb_name, &g_gdb_register_map_arm[i].nub_info)) + { + RegisterEntryNotAvailable (&g_gdb_register_map_arm[i]); + assert (g_gdb_register_map_arm[i].gdb_size <= MAX_REGISTER_BYTE_SIZE); + } + } + g_reg_entries = g_gdb_register_map_arm; + g_num_reg_entries = sizeof (g_gdb_register_map_arm) / sizeof (register_map_entry_t); + } +} + + +#elif defined (__i386__) + +register_map_entry_t +g_gdb_register_map_i386[] = +{ + { 0, 4, "eax" , {0}, NULL, 0 }, + { 1, 4, "ecx" , {0}, NULL, 0 }, + { 2, 4, "edx" , {0}, NULL, 0 }, + { 3, 4, "ebx" , {0}, NULL, 0 }, + { 4, 4, "esp" , {0}, NULL, 1 }, + { 5, 4, "ebp" , {0}, NULL, 1 }, + { 6, 4, "esi" , {0}, NULL, 0 }, + { 7, 4, "edi" , {0}, NULL, 0 }, + { 8, 4, "eip" , {0}, NULL, 1 }, + { 9, 4, "eflags" , {0}, NULL, 0 }, + { 10, 4, "cs" , {0}, NULL, 0 }, + { 11, 4, "ss" , {0}, NULL, 0 }, + { 12, 4, "ds" , {0}, NULL, 0 }, + { 13, 4, "es" , {0}, NULL, 0 }, + { 14, 4, "fs" , {0}, NULL, 0 }, + { 15, 4, "gs" , {0}, NULL, 0 }, + { 16, 10, "stmm0" , {0}, NULL, 0 }, + { 17, 10, "stmm1" , {0}, NULL, 0 }, + { 18, 10, "stmm2" , {0}, NULL, 0 }, + { 19, 10, "stmm3" , {0}, NULL, 0 }, + { 20, 10, "stmm4" , {0}, NULL, 0 }, + { 21, 10, "stmm5" , {0}, NULL, 0 }, + { 22, 10, "stmm6" , {0}, NULL, 0 }, + { 23, 10, "stmm7" , {0}, NULL, 0 }, + { 24, 4, "fctrl" , {0}, NULL, 0 }, + { 25, 4, "fstat" , {0}, NULL, 0 }, + { 26, 4, "ftag" , {0}, NULL, 0 }, + { 27, 4, "fiseg" , {0}, NULL, 0 }, + { 28, 4, "fioff" , {0}, NULL, 0 }, + { 29, 4, "foseg" , {0}, NULL, 0 }, + { 30, 4, "fooff" , {0}, NULL, 0 }, + { 31, 4, "fop" , {0}, NULL, 0 }, + { 32, 16, "xmm0" , {0}, NULL, 0 }, + { 33, 16, "xmm1" , {0}, NULL, 0 }, + { 34, 16, "xmm2" , {0}, NULL, 0 }, + { 35, 16, "xmm3" , {0}, NULL, 0 }, + { 36, 16, "xmm4" , {0}, NULL, 0 }, + { 37, 16, "xmm5" , {0}, NULL, 0 }, + { 38, 16, "xmm6" , {0}, NULL, 0 }, + { 39, 16, "xmm7" , {0}, NULL, 0 }, + { 40, 4, "mxcsr" , {0}, NULL, 0 }, +}; + +void +RNBRemote::InitializeRegisters (int use_native_registers) +{ + if (use_native_registers) + { + RNBRemote::InitializeNativeRegisters(); + } + else + { + const size_t num_regs = sizeof (g_gdb_register_map_i386) / sizeof (register_map_entry_t); + for (uint32_t i=0; i<num_regs; ++i) + { + if (!DNBGetRegisterInfoByName (g_gdb_register_map_i386[i].gdb_name, &g_gdb_register_map_i386[i].nub_info)) + { + RegisterEntryNotAvailable (&g_gdb_register_map_i386[i]); + assert (g_gdb_register_map_i386[i].gdb_size <= MAX_REGISTER_BYTE_SIZE); + } + } + g_reg_entries = g_gdb_register_map_i386; + g_num_reg_entries = sizeof (g_gdb_register_map_i386) / sizeof (register_map_entry_t); + } +} + + +#elif defined (__x86_64__) + +register_map_entry_t +g_gdb_register_map_x86_64[] = +{ + { 0, 8, "rax" , {0}, NULL, 0 }, + { 1, 8, "rbx" , {0}, NULL, 0 }, + { 2, 8, "rcx" , {0}, NULL, 0 }, + { 3, 8, "rdx" , {0}, NULL, 0 }, + { 4, 8, "rsi" , {0}, NULL, 0 }, + { 5, 8, "rdi" , {0}, NULL, 0 }, + { 6, 8, "rbp" , {0}, NULL, 1 }, + { 7, 8, "rsp" , {0}, NULL, 1 }, + { 8, 8, "r8" , {0}, NULL, 0 }, + { 9, 8, "r9" , {0}, NULL, 0 }, + { 10, 8, "r10" , {0}, NULL, 0 }, + { 11, 8, "r11" , {0}, NULL, 0 }, + { 12, 8, "r12" , {0}, NULL, 0 }, + { 13, 8, "r13" , {0}, NULL, 0 }, + { 14, 8, "r14" , {0}, NULL, 0 }, + { 15, 8, "r15" , {0}, NULL, 0 }, + { 16, 8, "rip" , {0}, NULL, 1 }, + { 17, 4, "rflags", {0}, NULL, 0 }, + { 18, 4, "cs" , {0}, NULL, 0 }, + { 19, 4, "ss" , {0}, NULL, 0 }, + { 20, 4, "ds" , {0}, NULL, 0 }, + { 21, 4, "es" , {0}, NULL, 0 }, + { 22, 4, "fs" , {0}, NULL, 0 }, + { 23, 4, "gs" , {0}, NULL, 0 }, + { 24, 10, "stmm0" , {0}, NULL, 0 }, + { 25, 10, "stmm1" , {0}, NULL, 0 }, + { 26, 10, "stmm2" , {0}, NULL, 0 }, + { 27, 10, "stmm3" , {0}, NULL, 0 }, + { 28, 10, "stmm4" , {0}, NULL, 0 }, + { 29, 10, "stmm5" , {0}, NULL, 0 }, + { 30, 10, "stmm6" , {0}, NULL, 0 }, + { 31, 10, "stmm7" , {0}, NULL, 0 }, + { 32, 4, "fctrl" , {0}, NULL, 0 }, + { 33, 4, "fstat" , {0}, NULL, 0 }, + { 34, 4, "ftag" , {0}, NULL, 0 }, + { 35, 4, "fiseg" , {0}, NULL, 0 }, + { 36, 4, "fioff" , {0}, NULL, 0 }, + { 37, 4, "foseg" , {0}, NULL, 0 }, + { 38, 4, "fooff" , {0}, NULL, 0 }, + { 39, 4, "fop" , {0}, NULL, 0 }, + { 40, 16, "xmm0" , {0}, NULL, 0 }, + { 41, 16, "xmm1" , {0}, NULL, 0 }, + { 42, 16, "xmm2" , {0}, NULL, 0 }, + { 43, 16, "xmm3" , {0}, NULL, 0 }, + { 44, 16, "xmm4" , {0}, NULL, 0 }, + { 45, 16, "xmm5" , {0}, NULL, 0 }, + { 46, 16, "xmm6" , {0}, NULL, 0 }, + { 47, 16, "xmm7" , {0}, NULL, 0 }, + { 48, 16, "xmm8" , {0}, NULL, 0 }, + { 49, 16, "xmm9" , {0}, NULL, 0 }, + { 50, 16, "xmm10" , {0}, NULL, 0 }, + { 51, 16, "xmm11" , {0}, NULL, 0 }, + { 52, 16, "xmm12" , {0}, NULL, 0 }, + { 53, 16, "xmm13" , {0}, NULL, 0 }, + { 54, 16, "xmm14" , {0}, NULL, 0 }, + { 55, 16, "xmm15" , {0}, NULL, 0 }, + { 56, 4, "mxcsr" , {0}, NULL, 0 } +}; + +void +RNBRemote::InitializeRegisters (int use_native_registers) +{ + if (use_native_registers) + { + RNBRemote::InitializeNativeRegisters(); + } + else + { + const size_t num_regs = sizeof (g_gdb_register_map_x86_64) / sizeof (register_map_entry_t); + for (uint32_t i=0; i<num_regs; ++i) + { + if (!DNBGetRegisterInfoByName (g_gdb_register_map_x86_64[i].gdb_name, &g_gdb_register_map_x86_64[i].nub_info)) + { + RegisterEntryNotAvailable (&g_gdb_register_map_x86_64[i]); + assert (g_gdb_register_map_x86_64[i].gdb_size < MAX_REGISTER_BYTE_SIZE); + } + } + g_reg_entries = g_gdb_register_map_x86_64; + g_num_reg_entries = sizeof (g_gdb_register_map_x86_64) / sizeof (register_map_entry_t); + } +} + + +#else + +void +RNBRemote::InitializeRegisters (int use_native_registers) +{ + // No choice, we don't have a GDB register definition for this arch. + RNBRemote::InitializeNativeRegisters(); +} + +#endif + + +void +RNBRemote::InitializeNativeRegisters() +{ + if (g_dynamic_register_map.empty()) + { + nub_size_t num_reg_sets = 0; + const DNBRegisterSetInfo *reg_sets = DNBGetRegisterSetInfo (&num_reg_sets); + + assert (num_reg_sets > 0 && reg_sets != NULL); + + uint32_t regnum = 0; + for (nub_size_t set = 0; set < num_reg_sets; ++set) + { + if (reg_sets[set].registers == NULL) + continue; + + for (uint32_t reg=0; reg < reg_sets[set].num_registers; ++reg) + { + register_map_entry_t reg_entry = { + regnum++, // register number starts at zero and goes up with no gaps + reg_sets[set].registers[reg].size, // register size in bytes + reg_sets[set].registers[reg].name, // register name + reg_sets[set].registers[reg], // DNBRegisterInfo + NULL, // Value to print if case we fail to reg this register (if this is NULL, we will return an error) + reg_sets[set].registers[reg].reg_generic != INVALID_NUB_REGNUM}; + + g_dynamic_register_map.push_back (reg_entry); + } + } + g_reg_entries = g_dynamic_register_map.data(); + g_num_reg_entries = g_dynamic_register_map.size(); + } +} + + +const register_map_entry_t * +register_mapping_by_regname (const char *n) +{ + for (uint32_t reg = 0; reg < g_num_reg_entries; reg++) + { + if (strcmp (g_reg_entries[reg].gdb_name, n) == 0) + return &g_reg_entries[reg]; + } + return NULL; +} + +/* The inferior has stopped executing; send a packet + to gdb to let it know. */ + +void +RNBRemote::NotifyThatProcessStopped (void) +{ + RNBRemote::HandlePacket_last_signal (""); + return; +} + + +/* `A arglen,argnum,arg,...' + Update the inferior context CTX with the program name and arg + list. + The documentation for this packet is underwhelming but my best reading + of this is that it is a series of (len, position #, arg)'s, one for + each argument with "arg" ``hex encoded'' (two 0-9a-f chars?). + Why we need BOTH a "len" and a hex encoded "arg" is beyond me - either + is sufficient to get around the "," position separator escape issue. + + e.g. our best guess for a valid 'A' packet for "gdb -q a.out" is + + 6,0,676462,4,1,2d71,10,2,612e6f7574 + + Note that "argnum" and "arglen" are numbers in base 10. Again, that's + not documented either way but I'm assuming it's so. */ + +rnb_err_t +RNBRemote::HandlePacket_A (const char *p) +{ + if (p == NULL || *p == '\0') + { + return HandlePacket_ILLFORMED ("Null packet for 'A' pkt"); + } + p++; + if (p == '\0' || !isdigit (*p)) + { + return HandlePacket_ILLFORMED ("arglen not specified on 'A' pkt"); + } + + /* I promise I don't modify it anywhere in this function. strtoul()'s + 2nd arg has to be non-const which makes it problematic to step + through the string easily. */ + char *buf = const_cast<char *>(p); + + RNBContext& ctx = Context(); + + while (*buf != '\0') + { + int arglen, argnum; + std::string arg; + char *c; + + errno = 0; + arglen = strtoul (buf, &c, 10); + if (errno != 0 && arglen == 0) + { + return HandlePacket_ILLFORMED ("arglen not a number on 'A' pkt"); + } + if (*c != ',') + { + return HandlePacket_ILLFORMED ("arglen not followed by comma on 'A' pkt"); + } + buf = c + 1; + + errno = 0; + argnum = strtoul (buf, &c, 10); + if (errno != 0 && argnum == 0) + { + return HandlePacket_ILLFORMED ("argnum not a number on 'A' pkt"); + } + if (*c != ',') + { + return HandlePacket_ILLFORMED ("arglen not followed by comma on 'A' pkt"); + } + buf = c + 1; + + c = buf; + buf = buf + arglen; + while (c < buf && *c != '\0' && c + 1 < buf && *(c + 1) != '\0') + { + char smallbuf[3]; + smallbuf[0] = *c; + smallbuf[1] = *(c + 1); + smallbuf[2] = '\0'; + + errno = 0; + int ch = strtoul (smallbuf, NULL, 16); + if (errno != 0 && ch == 0) + { + return HandlePacket_ILLFORMED ("non-hex char in arg on 'A' pkt"); + } + + arg.push_back(ch); + c += 2; + } + + ctx.PushArgument (arg.c_str()); + if (*buf == ',') + buf++; + } + SendPacket ("OK"); + + return rnb_success; +} + +/* `H c t' + Set the thread for subsequent actions; 'c' for step/continue ops, + 'g' for other ops. -1 means all threads, 0 means any thread. */ + +rnb_err_t +RNBRemote::HandlePacket_H (const char *p) +{ + p++; // skip 'H' + if (*p != 'c' && *p != 'g') + { + return HandlePacket_ILLFORMED ("Missing 'c' or 'g' type in H packet"); + } + + if (!m_ctx.HasValidProcessID()) + { + // We allow gdb to connect to a server that hasn't started running + // the target yet. gdb still wants to ask questions about it and + // freaks out if it gets an error. So just return OK here. + } + + errno = 0; + nub_thread_t tid = strtoul (p + 1, NULL, 16); + if (errno != 0 && tid == 0) + { + return HandlePacket_ILLFORMED ("Invalid thread number in H packet"); + } + if (*p == 'c') + SetContinueThread (tid); + if (*p == 'g') + SetCurrentThread (tid); + + return SendPacket ("OK"); +} + + +rnb_err_t +RNBRemote::HandlePacket_qLaunchSuccess (const char *p) +{ + if (m_ctx.HasValidProcessID() || m_ctx.LaunchStatus().Error() == 0) + return SendPacket("OK"); + std::ostringstream ret_str; + std::string status_str; + ret_str << "E" << m_ctx.LaunchStatusAsString(status_str); + + return SendPacket (ret_str.str()); +} + +rnb_err_t +RNBRemote::HandlePacket_qShlibInfoAddr (const char *p) +{ + if (m_ctx.HasValidProcessID()) + { + nub_addr_t shlib_info_addr = DNBProcessGetSharedLibraryInfoAddress(m_ctx.ProcessID()); + if (shlib_info_addr != INVALID_NUB_ADDRESS) + { + std::ostringstream ostrm; + ostrm << RAW_HEXBASE << shlib_info_addr; + return SendPacket (ostrm.str ()); + } + } + return SendPacket ("E44"); +} + +rnb_err_t +RNBRemote::HandlePacket_qStepPacketSupported (const char *p) +{ + // Normally the "s" packet is mandatory, yet in gdb when using ARM, they + // get around the need for this packet by implementing software single + // stepping from gdb. Current versions of debugserver do support the "s" + // packet, yet some older versions do not. We need a way to tell if this + // packet is supported so we can disable software single stepping in gdb + // for remote targets (so the "s" packet will get used). + return SendPacket("OK"); +} + +rnb_err_t +RNBRemote::HandlePacket_qThreadStopInfo (const char *p) +{ + p += strlen ("qThreadStopInfo"); + nub_thread_t tid = strtoul(p, 0, 16); + return SendStopReplyPacketForThread (tid); +} + +rnb_err_t +RNBRemote::HandlePacket_qThreadInfo (const char *p) +{ + // We allow gdb to connect to a server that hasn't started running + // the target yet. gdb still wants to ask questions about it and + // freaks out if it gets an error. So just return OK here. + nub_process_t pid = m_ctx.ProcessID(); + if (pid == INVALID_NUB_PROCESS) + return SendPacket ("OK"); + + // Only "qfThreadInfo" and "qsThreadInfo" get into this function so + // we only need to check the second byte to tell which is which + if (p[1] == 'f') + { + nub_size_t numthreads = DNBProcessGetNumThreads (pid); + std::ostringstream ostrm; + ostrm << "m"; + bool first = true; + for (nub_size_t i = 0; i < numthreads; ++i) + { + if (first) + first = false; + else + ostrm << ","; + nub_thread_t th = DNBProcessGetThreadAtIndex (pid, i); + ostrm << std::hex << th; + } + return SendPacket (ostrm.str ()); + } + else + { + return SendPacket ("l"); + } +} + +rnb_err_t +RNBRemote::HandlePacket_qThreadExtraInfo (const char *p) +{ + // We allow gdb to connect to a server that hasn't started running + // the target yet. gdb still wants to ask questions about it and + // freaks out if it gets an error. So just return OK here. + nub_process_t pid = m_ctx.ProcessID(); + if (pid == INVALID_NUB_PROCESS) + return SendPacket ("OK"); + + /* This is supposed to return a string like 'Runnable' or + 'Blocked on Mutex'. + The returned string is formatted like the "A" packet - a + sequence of letters encoded in as 2-hex-chars-per-letter. */ + p += strlen ("qThreadExtraInfo"); + if (*p++ != ',') + return HandlePacket_ILLFORMED ("Ill formed qThreadExtraInfo packet"); + errno = 0; + nub_thread_t tid = strtoul (p, NULL, 16); + if (errno != 0 && tid == 0) + { + return HandlePacket_ILLFORMED ("Invalid thread number in qThreadExtraInfo packet"); + } + + const char * threadInfo = DNBThreadGetInfo(pid, tid); + if (threadInfo != NULL && threadInfo[0]) + { + return SendHexEncodedBytePacket(NULL, threadInfo, strlen(threadInfo), NULL); + } + else + { + // "OK" == 4f6b + // Return "OK" as a ASCII hex byte stream if things go wrong + return SendPacket ("4f6b"); + } + + return SendPacket (""); +} + +rnb_err_t +RNBRemote::HandlePacket_qC (const char *p) +{ + nub_process_t pid; + std::ostringstream rep; + // If we haven't run the process yet, we tell the debugger the + // pid is 0. That way it can know to tell use to run later on. + if (m_ctx.HasValidProcessID()) + pid = m_ctx.ProcessID(); + else + pid = 0; + rep << "QC" << std::hex << pid; + return SendPacket (rep.str()); +} + +rnb_err_t +RNBRemote::HandlePacket_qRegisterInfo (const char *p) +{ + p += strlen ("qRegisterInfo"); + + nub_size_t num_reg_sets = 0; + const DNBRegisterSetInfo *reg_set_info = DNBGetRegisterSetInfo (&num_reg_sets); + uint32_t reg_num = strtoul(p, 0, 16); + + if (reg_num < g_num_reg_entries) + { + const register_map_entry_t *reg_entry = &g_reg_entries[reg_num]; + std::ostringstream ostrm; + ostrm << "name:" << reg_entry->gdb_name << ';'; + + if (reg_entry->nub_info.name && ::strcmp (reg_entry->gdb_name, reg_entry->nub_info.name)) + ostrm << "alt-name:" << reg_entry->nub_info.name << ';'; + else if (reg_entry->nub_info.alt && ::strcmp (reg_entry->gdb_name, reg_entry->nub_info.alt)) + ostrm << "alt-name:" << reg_entry->nub_info.alt << ';'; + + ostrm << "bitsize:" << std::dec << reg_entry->gdb_size * 8 << ';'; + ostrm << "offset:" << std::dec << reg_entry->nub_info.offset << ';'; + + switch (reg_entry->nub_info.type) + { + case Uint: ostrm << "encoding:uint;"; break; + case Sint: ostrm << "encoding:sint;"; break; + case IEEE754: ostrm << "encoding:ieee754;"; break; + case Vector: ostrm << "encoding:vector;"; break; + } + + switch (reg_entry->nub_info.format) + { + case Binary: ostrm << "format:binary;"; break; + case Decimal: ostrm << "format:decimal;"; break; + case Hex: ostrm << "format:hex;"; break; + case Float: ostrm << "format:float;"; break; + case VectorOfSInt8: ostrm << "format:vector-sint8;"; break; + case VectorOfUInt8: ostrm << "format:vector-uint8;"; break; + case VectorOfSInt16: ostrm << "format:vector-sint16;"; break; + case VectorOfUInt16: ostrm << "format:vector-uint16;"; break; + case VectorOfSInt32: ostrm << "format:vector-sint32;"; break; + case VectorOfUInt32: ostrm << "format:vector-uint32;"; break; + case VectorOfFloat32: ostrm << "format:vector-float32;"; break; + case VectorOfUInt128: ostrm << "format:vector-uint128;"; break; + }; + + if (reg_set_info && reg_entry->nub_info.set < num_reg_sets) + ostrm << "set:" << reg_set_info[reg_entry->nub_info.set].name << ';'; + + + if (g_reg_entries != g_dynamic_register_map.data()) + { + if (reg_entry->nub_info.reg_gdb != INVALID_NUB_REGNUM && reg_entry->nub_info.reg_gdb != reg_num) + { + printf("register %s is getting gdb reg_num of %u when the register info says %u\n", + reg_entry->gdb_name, reg_num, reg_entry->nub_info.reg_gdb); + } + } + + if (reg_entry->nub_info.reg_gcc != INVALID_NUB_REGNUM) + ostrm << "gcc:" << std::dec << reg_entry->nub_info.reg_gcc << ';'; + + if (reg_entry->nub_info.reg_dwarf != INVALID_NUB_REGNUM) + ostrm << "dwarf:" << std::dec << reg_entry->nub_info.reg_dwarf << ';'; + + + switch (reg_entry->nub_info.reg_generic) + { + case GENERIC_REGNUM_FP: ostrm << "generic:fp;"; break; + case GENERIC_REGNUM_PC: ostrm << "generic:pc;"; break; + case GENERIC_REGNUM_SP: ostrm << "generic:sp;"; break; + case GENERIC_REGNUM_RA: ostrm << "generic:ra;"; break; + case GENERIC_REGNUM_FLAGS: ostrm << "generic:flags;"; break; + default: break; + } + + return SendPacket (ostrm.str ()); + } + return SendPacket ("E45"); +} + + +/* This expects a packet formatted like + + QSetLogging:bitmask=LOG_ALL|LOG_RNB_REMOTE; + + with the "QSetLogging:" already removed from the start. Maybe in the + future this packet will include other keyvalue pairs like + + QSetLogging:bitmask=LOG_ALL;mode=asl; + */ + +rnb_err_t +set_logging (const char *p) +{ + int bitmask = 0; + while (p && *p != '\0') + { + if (strncmp (p, "bitmask=", sizeof ("bitmask=") - 1) == 0) + { + p += sizeof ("bitmask=") - 1; + while (p && *p != '\0' && *p != ';') + { + if (*p == '|') + p++; + if (strncmp (p, "LOG_VERBOSE", sizeof ("LOG_VERBOSE") - 1) == 0) + { + p += sizeof ("LOG_VERBOSE") - 1; + bitmask |= LOG_VERBOSE; + } + else if (strncmp (p, "LOG_PROCESS", sizeof ("LOG_PROCESS") - 1) == 0) + { + p += sizeof ("LOG_PROCESS") - 1; + bitmask |= LOG_PROCESS; + } + else if (strncmp (p, "LOG_THREAD", sizeof ("LOG_THREAD") - 1) == 0) + { + p += sizeof ("LOG_THREAD") - 1; + bitmask |= LOG_THREAD; + } + else if (strncmp (p, "LOG_EXCEPTIONS", sizeof ("LOG_EXCEPTIONS") - 1) == 0) + { + p += sizeof ("LOG_EXCEPTIONS") - 1; + bitmask |= LOG_EXCEPTIONS; + } + else if (strncmp (p, "LOG_SHLIB", sizeof ("LOG_SHLIB") - 1) == 0) + { + p += sizeof ("LOG_SHLIB") - 1; + bitmask |= LOG_SHLIB; + } + else if (strncmp (p, "LOG_MEMORY", sizeof ("LOG_MEMORY") - 1) == 0) + { + p += sizeof ("LOG_MEMORY") - 1; + bitmask |= LOG_MEMORY; + } + else if (strncmp (p, "LOG_MEMORY_DATA_SHORT", sizeof ("LOG_MEMORY_DATA_SHORT") - 1) == 0) + { + p += sizeof ("LOG_MEMORY_DATA_SHORT") - 1; + bitmask |= LOG_MEMORY_DATA_SHORT; + } + else if (strncmp (p, "LOG_MEMORY_DATA_LONG", sizeof ("LOG_MEMORY_DATA_LONG") - 1) == 0) + { + p += sizeof ("LOG_MEMORY_DATA_LONG") - 1; + bitmask |= LOG_MEMORY_DATA_LONG; + } + else if (strncmp (p, "LOG_BREAKPOINTS", sizeof ("LOG_BREAKPOINTS") - 1) == 0) + { + p += sizeof ("LOG_BREAKPOINTS") - 1; + bitmask |= LOG_BREAKPOINTS; + } + else if (strncmp (p, "LOG_ALL", sizeof ("LOG_ALL") - 1) == 0) + { + p += sizeof ("LOG_ALL") - 1; + bitmask |= LOG_ALL; + } + else if (strncmp (p, "LOG_EVENTS", sizeof ("LOG_EVENTS") - 1) == 0) + { + p += sizeof ("LOG_EVENTS") - 1; + bitmask |= LOG_EVENTS; + } + else if (strncmp (p, "LOG_DEFAULT", sizeof ("LOG_DEFAULT") - 1) == 0) + { + p += sizeof ("LOG_DEFAULT") - 1; + bitmask |= LOG_DEFAULT; + } + else if (strncmp (p, "LOG_NONE", sizeof ("LOG_NONE") - 1) == 0) + { + p += sizeof ("LOG_NONE") - 1; + bitmask = 0; + } + else if (strncmp (p, "LOG_RNB_MINIMAL", sizeof ("LOG_RNB_MINIMAL") - 1) == 0) + { + p += sizeof ("LOG_RNB_MINIMAL") - 1; + bitmask |= LOG_RNB_MINIMAL; + } + else if (strncmp (p, "LOG_RNB_MEDIUM", sizeof ("LOG_RNB_MEDIUM") - 1) == 0) + { + p += sizeof ("LOG_RNB_MEDIUM") - 1; + bitmask |= LOG_RNB_MEDIUM; + } + else if (strncmp (p, "LOG_RNB_MAX", sizeof ("LOG_RNB_MAX") - 1) == 0) + { + p += sizeof ("LOG_RNB_MAX") - 1; + bitmask |= LOG_RNB_MAX; + } + else if (strncmp (p, "LOG_RNB_COMM", sizeof ("LOG_RNB_COMM") - 1) == 0) + { + p += sizeof ("LOG_RNB_COMM") - 1; + bitmask |= LOG_RNB_COMM; + } + else if (strncmp (p, "LOG_RNB_REMOTE", sizeof ("LOG_RNB_REMOTE") - 1) == 0) + { + p += sizeof ("LOG_RNB_REMOTE") - 1; + bitmask |= LOG_RNB_REMOTE; + } + else if (strncmp (p, "LOG_RNB_EVENTS", sizeof ("LOG_RNB_EVENTS") - 1) == 0) + { + p += sizeof ("LOG_RNB_EVENTS") - 1; + bitmask |= LOG_RNB_EVENTS; + } + else if (strncmp (p, "LOG_RNB_PROC", sizeof ("LOG_RNB_PROC") - 1) == 0) + { + p += sizeof ("LOG_RNB_PROC") - 1; + bitmask |= LOG_RNB_PROC; + } + else if (strncmp (p, "LOG_RNB_PACKETS", sizeof ("LOG_RNB_PACKETS") - 1) == 0) + { + p += sizeof ("LOG_RNB_PACKETS") - 1; + bitmask |= LOG_RNB_PACKETS; + } + else if (strncmp (p, "LOG_RNB_ALL", sizeof ("LOG_RNB_ALL") - 1) == 0) + { + p += sizeof ("LOG_RNB_ALL") - 1; + bitmask |= LOG_RNB_ALL; + } + else if (strncmp (p, "LOG_RNB_DEFAULT", sizeof ("LOG_RNB_DEFAULT") - 1) == 0) + { + p += sizeof ("LOG_RNB_DEFAULT") - 1; + bitmask |= LOG_RNB_DEFAULT; + } + else if (strncmp (p, "LOG_RNB_NONE", sizeof ("LOG_RNB_NONE") - 1) == 0) + { + p += sizeof ("LOG_RNB_NONE") - 1; + bitmask = 0; + } + else + { + /* Unrecognized logging bit; ignore it. */ + const char *c = strchr (p, '|'); + if (c) + { + p = c; + } + else + { + c = strchr (p, ';'); + if (c) + { + p = c; + } + else + { + // Improperly terminated word; just go to end of str + p = strchr (p, '\0'); + } + } + } + } + // Did we get a properly formatted logging bitmask? + if (*p == ';') + { + // Enable DNB logging + DNBLogSetLogCallback(ASLLogCallback, NULL); + DNBLogSetLogMask (bitmask); + p++; + } + } + // We're not going to support logging to a file for now. All logging + // goes through ASL. +#if 0 + else if (strncmp (p, "mode=", sizeof ("mode=") - 1) == 0) + { + p += sizeof ("mode=") - 1; + if (strncmp (p, "asl;", sizeof ("asl;") - 1) == 0) + { + DNBLogToASL (); + p += sizeof ("asl;") - 1; + } + else if (strncmp (p, "file;", sizeof ("file;") - 1) == 0) + { + DNBLogToFile (); + p += sizeof ("file;") - 1; + } + else + { + // Ignore unknown argument + const char *c = strchr (p, ';'); + if (c) + p = c + 1; + else + p = strchr (p, '\0'); + } + } + else if (strncmp (p, "filename=", sizeof ("filename=") - 1) == 0) + { + p += sizeof ("filename=") - 1; + const char *c = strchr (p, ';'); + if (c == NULL) + { + c = strchr (p, '\0'); + continue; + } + char *fn = (char *) alloca (c - p + 1); + strncpy (fn, p, c - p); + fn[c - p] = '\0'; + + // A file name of "asl" is special and is another way to indicate + // that logging should be done via ASL, not by file. + if (strcmp (fn, "asl") == 0) + { + DNBLogToASL (); + } + else + { + FILE *f = fopen (fn, "w"); + if (f) + { + DNBLogSetLogFile (f); + DNBEnableLogging (f, DNBLogGetLogMask ()); + DNBLogToFile (); + } + } + p = c + 1; + } +#endif /* #if 0 to enforce ASL logging only. */ + else + { + // Ignore unknown argument + const char *c = strchr (p, ';'); + if (c) + p = c + 1; + else + p = strchr (p, '\0'); + } + } + + return rnb_success; +} + + + +rnb_err_t +RNBRemote::HandlePacket_Q (const char *p) +{ + if (p == NULL || strlen (p) <= 1) + { + return HandlePacket_ILLFORMED ("No subtype specified in Q packet"); + } + + /* Switch to no-ack protocol mode after the "OK" packet is sent + and the ack for that comes back from gdb. */ + + if (strcmp (p, "QStartNoAckMode") == 0) + { + rnb_err_t result = SendPacket ("OK"); + m_noack_mode = true; + return result; + } + + if (strncmp (p, "QSetLogging:", sizeof ("QSetLogging:") - 1) == 0) + { + p += sizeof ("QSetLogging:") - 1; + rnb_err_t result = set_logging (p); + if (result == rnb_success) + return SendPacket ("OK"); + else + return SendPacket ("E35"); + } + + /* The number of characters in a packet payload that gdb is + prepared to accept. The packet-start char, packet-end char, + 2 checksum chars and terminating null character are not included + in this size. */ + if (strncmp (p, "QSetMaxPayloadSize:", sizeof ("QSetMaxPayloadSize:") - 1) == 0) + { + p += sizeof ("QSetMaxPayloadSize:") - 1; + errno = 0; + uint32_t size = strtoul (p, NULL, 16); + if (errno != 0 && size == 0) + { + return HandlePacket_ILLFORMED ("Invalid length in QSetMaxPayloadSize packet"); + } + m_max_payload_size = size; + return SendPacket ("OK"); + } + + /* This tells us the largest packet that gdb can handle. + i.e. the size of gdb's packet-reading buffer. + QSetMaxPayloadSize is preferred because it is less ambiguous. */ + + if (strncmp (p, "QSetMaxPacketSize:", sizeof ("QSetMaxPacketSize:") - 1) == 0) + { + p += sizeof ("QSetMaxPacketSize:") - 1; + errno = 0; + uint32_t size = strtoul (p, NULL, 16); + if (errno != 0 && size == 0) + { + return HandlePacket_ILLFORMED ("Invalid length in QSetMaxPacketSize packet"); + } + m_max_payload_size = size - 5; + return SendPacket ("OK"); + } + + /* This sets the environment for the target program. The packet is of the form: + + QEnvironment:VARIABLE=VALUE + + */ + + if (strncmp (p, "QEnvironment:", sizeof ("QEnvironment:") - 1) == 0) + { + DNBLogThreadedIf (LOG_RNB_REMOTE, "%8u RNBRemote::%s Handling QEnvironment: \"%s\"", + (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__, p); + + p += sizeof ("QEnvironment:") - 1; + RNBContext& ctx = Context(); + + ctx.PushEnvironment (p); + return SendPacket ("OK"); + } + + // Unrecognized Q packet + return SendPacket (""); +} + +void +append_hex_value (std::ostream& ostrm, const uint8_t* buf, size_t buf_size, bool swap) +{ + int i; + if (swap) + { + for (i = buf_size-1; i >= 0; i--) + ostrm << RAWHEX8(buf[i]); + } + else + { + for (i = 0; i < buf_size; i++) + ostrm << RAWHEX8(buf[i]); + } +} + + +void +register_value_in_hex_fixed_width +( + std::ostream& ostrm, + nub_process_t pid, + nub_thread_t tid, + const register_map_entry_t* reg + ) +{ + if (reg != NULL) + { + DNBRegisterValue val; + if (DNBThreadGetRegisterValueByID (pid, tid, reg->nub_info.set, reg->nub_info.reg, &val)) + { + append_hex_value (ostrm, val.value.v_uint8, reg->gdb_size, false); + } + else + { + // If we fail to read a regiser value, check if it has a default + // fail value. If it does, return this instead in case some of + // the registers are not available on the current system. + if (reg->gdb_size > 0) + { + if (reg->fail_value != NULL) + { + append_hex_value (ostrm, reg->fail_value, reg->gdb_size, false); + } + else + { + std::basic_string<uint8_t> zeros(reg->gdb_size, '\0'); + append_hex_value (ostrm, zeros.data(), zeros.size(), false); + } + } + } + } +} + + +void +gdb_regnum_with_fixed_width_hex_register_value +( + std::ostream& ostrm, + nub_process_t pid, + nub_thread_t tid, + const register_map_entry_t* reg + ) +{ + // Output the register number as 'NN:VVVVVVVV;' where NN is a 2 bytes HEX + // gdb register number, and VVVVVVVV is the correct number of hex bytes + // as ASCII for the register value. + if (reg != NULL) + { + ostrm << RAWHEX8(reg->gdb_regnum) << ':'; + register_value_in_hex_fixed_width (ostrm, pid, tid, reg); + ostrm << ';'; + } +} + +rnb_err_t +RNBRemote::SendStopReplyPacketForThread (nub_thread_t tid) +{ + const nub_process_t pid = m_ctx.ProcessID(); + if (pid == INVALID_NUB_PROCESS) + return SendPacket("E50"); + + struct DNBThreadStopInfo tid_stop_info; + + /* Fill the remaining space in this packet with as many registers + as we can stuff in there. */ + + if (DNBThreadGetStopReason (pid, tid, &tid_stop_info)) + { + std::ostringstream ostrm; + // Output the T packet with the thread + ostrm << 'T'; + int signum = tid_stop_info.details.signal.signo; + DNBLogThreadedIf (LOG_RNB_PROC, "%8d %s got signal signo = %u, exc_type = %u", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__, tid_stop_info.details.signal.signo, tid_stop_info.details.exception.type); + + // Translate any mach exceptions to gdb versions, unless they are + // common exceptions like a breakpoint or a soft signal. + switch (tid_stop_info.details.exception.type) + { + default: signum = 0; break; + case EXC_BREAKPOINT: signum = SIGTRAP; break; + case EXC_BAD_ACCESS: signum = TARGET_EXC_BAD_ACCESS; break; + case EXC_BAD_INSTRUCTION: signum = TARGET_EXC_BAD_INSTRUCTION; break; + case EXC_ARITHMETIC: signum = TARGET_EXC_ARITHMETIC; break; + case EXC_EMULATION: signum = TARGET_EXC_EMULATION; break; + case EXC_SOFTWARE: + if (tid_stop_info.details.exception.data_count == 2 && + tid_stop_info.details.exception.data[0] == EXC_SOFT_SIGNAL) + signum = tid_stop_info.details.exception.data[1]; + else + signum = TARGET_EXC_SOFTWARE; + break; + } + + ostrm << RAWHEX8(signum & 0xff); + + ostrm << std::hex << "thread:" << tid << ';'; + + const char *thread_name = DNBThreadGetName (pid, tid); + if (thread_name && thread_name[0]) + ostrm << std::hex << "name:" << thread_name << ';'; + + thread_identifier_info_data_t thread_ident_info; + if (DNBThreadGetIdentifierInfo (pid, tid, &thread_ident_info)) + { + if (thread_ident_info.dispatch_qaddr != 0) + ostrm << std::hex << "dispatchqaddr:" << thread_ident_info.dispatch_qaddr << ';'; + } + DNBRegisterValue reg_value; + for (uint32_t reg = 0; reg < g_num_reg_entries; reg++) + { + if (g_reg_entries[reg].expedite) + { + if (!DNBThreadGetRegisterValueByID (pid, tid, g_reg_entries[reg].nub_info.set, g_reg_entries[reg].nub_info.reg, ®_value)) + continue; + + gdb_regnum_with_fixed_width_hex_register_value (ostrm, pid, tid, &g_reg_entries[reg]); + } + } + + if (tid_stop_info.details.exception.type) + { + ostrm << "metype:" << std::hex << tid_stop_info.details.exception.type << ";"; + ostrm << "mecount:" << std::hex << tid_stop_info.details.exception.data_count << ";"; + for (int i = 0; i < tid_stop_info.details.exception.data_count; ++i) + ostrm << "medata:" << std::hex << tid_stop_info.details.exception.data[i] << ";"; + } + return SendPacket (ostrm.str ()); + } + return SendPacket("E51"); +} + +/* `?' + The stop reply packet - tell gdb what the status of the inferior is. + Often called the questionmark_packet. */ + +rnb_err_t +RNBRemote::HandlePacket_last_signal (const char *unused) +{ + if (!m_ctx.HasValidProcessID()) + { + // Inferior is not yet specified/running + return SendPacket ("E02"); + } + + nub_process_t pid = m_ctx.ProcessID(); + nub_state_t pid_state = DNBProcessGetState (pid); + + switch (pid_state) + { + case eStateAttaching: + case eStateLaunching: + case eStateRunning: + case eStateStepping: + return rnb_success; // Ignore + + case eStateSuspended: + case eStateStopped: + case eStateCrashed: + { + nub_thread_t tid = DNBProcessGetCurrentThread (pid); + // Make sure we set the current thread so g and p packets return + // the data the gdb will expect. + SetCurrentThread (tid); + + SendStopReplyPacketForThread (tid); + } + break; + + case eStateInvalid: + case eStateUnloaded: + case eStateExited: + { + char pid_exited_packet[16] = ""; + int pid_status = 0; + // Process exited with exit status + if (!DNBProcessGetExitStatus(pid, &pid_status)) + pid_status = 0; + + if (pid_status) + { + if (WIFEXITED (pid_status)) + snprintf (pid_exited_packet, sizeof(pid_exited_packet), "W%02x", WEXITSTATUS (pid_status)); + else if (WIFSIGNALED (pid_status)) + snprintf (pid_exited_packet, sizeof(pid_exited_packet), "X%02x", WEXITSTATUS (pid_status)); + else if (WIFSTOPPED (pid_status)) + snprintf (pid_exited_packet, sizeof(pid_exited_packet), "S%02x", WSTOPSIG (pid_status)); + } + + // If we have an empty exit packet, lets fill one in to be safe. + if (!pid_exited_packet[0]) + { + strncpy (pid_exited_packet, "W00", sizeof(pid_exited_packet)-1); + pid_exited_packet[sizeof(pid_exited_packet)-1] = '\0'; + } + + return SendPacket (pid_exited_packet); + } + break; + } + return rnb_success; +} + +rnb_err_t +RNBRemote::HandlePacket_M (const char *p) +{ + if (p == NULL || p[0] == '\0' || strlen (p) < 3) + { + return HandlePacket_ILLFORMED ("Too short M packet"); + } + + char *c; + p++; + errno = 0; + nub_addr_t addr = strtoull (p, &c, 16); + if (errno != 0 && addr == 0) + { + return HandlePacket_ILLFORMED ("Invalid address in M packet"); + } + if (*c != ',') + { + return HandlePacket_ILLFORMED ("Comma sep missing in M packet"); + } + + /* Advance 'p' to the length part of the packet. */ + p += (c - p) + 1; + + errno = 0; + uint32_t length = strtoul (p, &c, 16); + if (errno != 0 && length == 0) + { + return HandlePacket_ILLFORMED ("Invalid length in M packet"); + } + if (length == 0) + { + return SendPacket ("OK"); + } + + if (*c != ':') + { + return HandlePacket_ILLFORMED ("Missing colon in M packet"); + } + /* Advance 'p' to the data part of the packet. */ + p += (c - p) + 1; + + int datalen = strlen (p); + if (datalen & 0x1) + { + return HandlePacket_ILLFORMED ("Uneven # of hex chars for data in M packet"); + } + if (datalen == 0) + { + return SendPacket ("OK"); + } + + uint8_t *buf = (uint8_t *) alloca (datalen / 2); + uint8_t *i = buf; + + while (*p != '\0' && *(p + 1) != '\0') + { + char hexbuf[3]; + hexbuf[0] = *p; + hexbuf[1] = *(p + 1); + hexbuf[2] = '\0'; + errno = 0; + uint8_t byte = strtoul (hexbuf, NULL, 16); + if (errno != 0 && byte == 0) + { + return HandlePacket_ILLFORMED ("Invalid hex byte in M packet"); + } + *i++ = byte; + p += 2; + } + + nub_size_t wrote = DNBProcessMemoryWrite (m_ctx.ProcessID(), addr, length, buf); + if (wrote != length) + return SendPacket ("E09"); + else + return SendPacket ("OK"); +} + + +rnb_err_t +RNBRemote::HandlePacket_m (const char *p) +{ + if (p == NULL || p[0] == '\0' || strlen (p) < 3) + { + return HandlePacket_ILLFORMED ("Too short m packet"); + } + + char *c; + p++; + errno = 0; + nub_addr_t addr = strtoull (p, &c, 16); + if (errno != 0 && addr == 0) + { + return HandlePacket_ILLFORMED ("Invalid address in m packet"); + } + if (*c != ',') + { + return HandlePacket_ILLFORMED ("Comma sep missing in m packet"); + } + + /* Advance 'p' to the length part of the packet. */ + p += (c - p) + 1; + + errno = 0; + uint32_t length = strtoul (p, NULL, 16); + if (errno != 0 && length == 0) + { + return HandlePacket_ILLFORMED ("Invalid length in m packet"); + } + if (length == 0) + { + return SendPacket (""); + } + + uint8_t buf[length]; + int bytes_read = DNBProcessMemoryRead (m_ctx.ProcessID(), addr, length, buf); + if (bytes_read == 0) + { + return SendPacket ("E08"); + } + + // "The reply may contain fewer bytes than requested if the server was able + // to read only part of the region of memory." + length = bytes_read; + + std::ostringstream ostrm; + for (int i = 0; i < length; i++) + ostrm << RAWHEX8(buf[i]); + return SendPacket (ostrm.str ()); +} + +rnb_err_t +RNBRemote::HandlePacket_X (const char *p) +{ + if (p == NULL || p[0] == '\0' || strlen (p) < 3) + { + return HandlePacket_ILLFORMED ("Too short X packet"); + } + + char *c; + p++; + errno = 0; + nub_addr_t addr = strtoull (p, &c, 16); + if (errno != 0 && addr == 0) + { + return HandlePacket_ILLFORMED ("Invalid address in X packet"); + } + if (*c != ',') + { + return HandlePacket_ILLFORMED ("Comma sep missing in X packet"); + } + + /* Advance 'p' to the length part of the packet. */ + p += (c - p) + 1; + + errno = 0; + int length = strtoul (p, NULL, 16); + if (errno != 0 && length == 0) + { + return HandlePacket_ILLFORMED ("Invalid length in m packet"); + } + + // I think gdb sends a zero length write request to test whether this + // packet is accepted. + if (length == 0) + { + return SendPacket ("OK"); + } + + std::vector<uint8_t> data = decode_binary_data (c, -1); + std::vector<uint8_t>::const_iterator it; + uint8_t *buf = (uint8_t *) alloca (data.size ()); + uint8_t *i = buf; + for (it = data.begin (); it != data.end (); ++it) + { + *i++ = *it; + } + + nub_size_t wrote = DNBProcessMemoryWrite (m_ctx.ProcessID(), addr, data.size(), buf); + if (wrote != data.size ()) + return SendPacket ("E08"); + return SendPacket ("OK"); +} + +/* `g' -- read registers + Get the contents of the registers for the current thread, + send them to gdb. + Should the setting of the Hg packet determine which thread's registers + are returned? */ + +rnb_err_t +RNBRemote::HandlePacket_g (const char *p) +{ + std::ostringstream ostrm; + if (!m_ctx.HasValidProcessID()) + { + return SendPacket ("E11"); + } + nub_process_t pid = m_ctx.ProcessID (); + nub_thread_t tid = GetCurrentThread(); + + if (m_use_native_regs) + { + // Get the register context size first by calling with NULL buffer + nub_size_t reg_ctx_size = DNBThreadGetRegisterContext(pid, tid, NULL, 0); + if (reg_ctx_size) + { + // Now allocate enough space for the entire register context + std::vector<uint8_t> reg_ctx; + reg_ctx.resize(reg_ctx_size); + // Now read the register context + reg_ctx_size = DNBThreadGetRegisterContext(pid, tid, ®_ctx[0], reg_ctx.size()); + if (reg_ctx_size) + { + append_hex_value (ostrm, reg_ctx.data(), reg_ctx.size(), false); + return SendPacket (ostrm.str ()); + } + } + } + + for (uint32_t reg = 0; reg < g_num_reg_entries; reg++) + register_value_in_hex_fixed_width (ostrm, pid, tid, &g_reg_entries[reg]); + + return SendPacket (ostrm.str ()); +} + +/* `G XXX...' -- write registers + How is the thread for these specified, beyond "the current thread"? + Does gdb actually use the Hg packet to set this? */ + +rnb_err_t +RNBRemote::HandlePacket_G (const char *p) +{ + if (!m_ctx.HasValidProcessID()) + { + return SendPacket ("E11"); + } + StringExtractor packet(p); + packet.SetFilePos(1); // Skip the 'G' + + nub_process_t pid = m_ctx.ProcessID(); + nub_thread_t tid = GetCurrentThread(); + + if (m_use_native_regs) + { + // Get the register context size first by calling with NULL buffer + nub_size_t reg_ctx_size = DNBThreadGetRegisterContext(pid, tid, NULL, 0); + if (reg_ctx_size) + { + // Now allocate enough space for the entire register context + std::vector<uint8_t> reg_ctx; + reg_ctx.resize(reg_ctx_size); + + if (packet.GetHexBytes (®_ctx[0], reg_ctx.size(), 0xcc) == reg_ctx.size()) + { + // Now write the register context + reg_ctx_size = DNBThreadSetRegisterContext(pid, tid, reg_ctx.data(), reg_ctx.size()); + if (reg_ctx_size == reg_ctx.size()) + return SendPacket ("OK"); + else + return SendPacket ("E55"); + } + } + } + + + DNBRegisterValue reg_value; + for (uint32_t reg = 0; reg < g_num_reg_entries; reg++) + { + const register_map_entry_t *reg_entry = &g_reg_entries[reg]; + + reg_value.info = reg_entry->nub_info; + if (packet.GetHexBytes (reg_value.value.v_sint8, reg_entry->gdb_size, 0xcc) != reg_entry->gdb_size) + break; + + if (!DNBThreadSetRegisterValueByID (pid, tid, reg_entry->nub_info.set, reg_entry->nub_info.reg, ®_value)) + return SendPacket ("E15"); + } + return SendPacket ("OK"); +} + +static bool +RNBRemoteShouldCancelCallback (void *not_used) +{ + RNBRemoteSP remoteSP(g_remoteSP); + if (remoteSP.get() != NULL) + { + RNBRemote* remote = remoteSP.get(); + if (remote->Comm().IsConnected()) + return false; + else + return true; + } + return true; +} + + +// FORMAT: _MXXXXXX,PPP +// XXXXXX: big endian hex chars +// PPP: permissions can be any combo of r w x chars +// +// RESPONSE: XXXXXX +// XXXXXX: hex address of the newly allocated memory +// EXX: error code +// +// EXAMPLES: +// _M123000,rw +// _M123000,rwx +// _M123000,xw + +rnb_err_t +RNBRemote::HandlePacket_AllocateMemory (const char *p) +{ + StringExtractor packet (p); + packet.SetFilePos(2); // Skip the "_M" + + nub_addr_t size = packet.GetHexMaxU64 (StringExtractor::BigEndian, 0); + if (size != 0) + { + if (packet.GetChar() == ',') + { + uint32_t permissions = 0; + char ch; + bool success = true; + while (success && (ch = packet.GetChar()) != '\0') + { + switch (ch) + { + case 'r': permissions |= eMemoryPermissionsReadable; break; + case 'w': permissions |= eMemoryPermissionsWritable; break; + case 'x': permissions |= eMemoryPermissionsExecutable; break; + default: success = false; break; + } + } + + if (success) + { + nub_addr_t addr = DNBProcessMemoryAllocate (m_ctx.ProcessID(), size, permissions); + if (addr != INVALID_NUB_ADDRESS) + { + std::ostringstream ostrm; + ostrm << RAW_HEXBASE << addr; + return SendPacket (ostrm.str ()); + } + } + } + } + return SendPacket ("E53"); +} + +// FORMAT: _mXXXXXX +// XXXXXX: address that was previosly allocated +// +// RESPONSE: XXXXXX +// OK: address was deallocated +// EXX: error code +// +// EXAMPLES: +// _m123000 + +rnb_err_t +RNBRemote::HandlePacket_DeallocateMemory (const char *p) +{ + StringExtractor packet (p); + packet.SetFilePos(2); // Skip the "_m" + nub_addr_t addr = packet.GetHexMaxU64 (StringExtractor::BigEndian, INVALID_NUB_ADDRESS); + + if (addr != INVALID_NUB_ADDRESS) + { + if (DNBProcessMemoryDeallocate (m_ctx.ProcessID(), addr)) + return SendPacket ("OK"); + } + return SendPacket ("E54"); +} + +/* + vAttach;pid + + Attach to a new process with the specified process ID. pid is a hexadecimal integer + identifying the process. If the stub is currently controlling a process, it is + killed. The attached process is stopped.This packet is only available in extended + mode (see extended mode). + + Reply: + "ENN" for an error + "Any Stop Reply Packet" for success + */ + +rnb_err_t +RNBRemote::HandlePacket_v (const char *p) +{ + if (strcmp (p, "vCont;c") == 0) + { + // Simple continue + return RNBRemote::HandlePacket_c("c"); + } + else if (strcmp (p, "vCont;s") == 0) + { + // Simple step + return RNBRemote::HandlePacket_s("s"); + } + else if (strstr (p, "vCont") == p) + { + rnb_err_t rnb_err = rnb_success; + typedef struct + { + nub_thread_t tid; + char action; + int signal; + } vcont_action_t; + + DNBThreadResumeActions thread_actions; + char *c = (char *)(p += strlen("vCont")); + char *c_end = c + strlen(c); + if (*c == '?') + return SendPacket ("vCont;c;C;s;S"); + + while (c < c_end && *c == ';') + { + ++c; // Skip the semi-colon + DNBThreadResumeAction thread_action; + thread_action.tid = INVALID_NUB_THREAD; + thread_action.state = eStateInvalid; + thread_action.signal = 0; + thread_action.addr = INVALID_NUB_ADDRESS; + + char action = *c++; + + switch (action) + { + case 'C': + errno = 0; + thread_action.signal = strtoul (c, &c, 16); + if (errno != 0) + return HandlePacket_ILLFORMED ("Could not parse signal in vCont packet"); + // Fall through to next case... + + case 'c': + // Continue + thread_action.state = eStateRunning; + break; + + case 'S': + errno = 0; + thread_action.signal = strtoul (c, &c, 16); + if (errno != 0) + return HandlePacket_ILLFORMED ("Could not parse signal in vCont packet"); + // Fall through to next case... + + case 's': + // Step + thread_action.state = eStateStepping; + break; + + break; + + default: + rnb_err = HandlePacket_ILLFORMED ("Unsupported action in vCont packet"); + break; + } + if (*c == ':') + { + errno = 0; + thread_action.tid = strtoul (++c, &c, 16); + if (errno != 0) + return HandlePacket_ILLFORMED ("Could not parse thread number in vCont packet"); + } + + thread_actions.Append (thread_action); + } + + // If a default action for all other threads wasn't mentioned + // then we should stop the threads + thread_actions.SetDefaultThreadActionIfNeeded (eStateStopped, 0); + DNBProcessResume(m_ctx.ProcessID(), thread_actions.GetFirst (), thread_actions.GetSize()); + return rnb_success; + } + else if (strstr (p, "vAttach") == p) + { + nub_process_t attach_pid = INVALID_NUB_PROCESS; + char err_str[1024]={'\0'}; + if (strstr (p, "vAttachWait;") == p) + { + p += strlen("vAttachWait;"); + std::string attach_name; + while (*p != '\0') + { + char smallbuf[3]; + smallbuf[0] = *p; + smallbuf[1] = *(p + 1); + smallbuf[2] = '\0'; + + errno = 0; + int ch = strtoul (smallbuf, NULL, 16); + if (errno != 0 && ch == 0) + { + return HandlePacket_ILLFORMED ("non-hex char in arg on 'vAttachWait' pkt"); + } + + attach_name.push_back(ch); + p += 2; + } + + attach_pid = DNBProcessAttachWait(attach_name.c_str (), m_ctx.LaunchFlavor(), NULL, 1000, err_str, sizeof(err_str), RNBRemoteShouldCancelCallback); + + } + else if (strstr (p, "vAttach;") == p) + { + p += strlen("vAttach;"); + char *end = NULL; + attach_pid = strtoul (p, &end, 16); // PID will be in hex, so use base 16 to decode + if (p != end && *end == '\0') + { + // Wait at most 30 second for attach + struct timespec attach_timeout_abstime; + DNBTimer::OffsetTimeOfDay(&attach_timeout_abstime, 30, 0); + attach_pid = DNBProcessAttach(attach_pid, &attach_timeout_abstime, err_str, sizeof(err_str)); + } + } + else + return HandlePacket_UNIMPLEMENTED(p); + + + if (attach_pid != INVALID_NUB_PROCESS) + { + if (m_ctx.ProcessID() != attach_pid) + m_ctx.SetProcessID(attach_pid); + // Send a stop reply packet to indicate we successfully attached! + NotifyThatProcessStopped (); + return rnb_success; + } + else + { + m_ctx.LaunchStatus().SetError(-1, DNBError::Generic); + if (err_str[0]) + m_ctx.LaunchStatus().SetErrorString(err_str); + else + m_ctx.LaunchStatus().SetErrorString("attach failed"); + return SendPacket ("E01"); // E01 is our magic error value for attach failed. + } + } + + // All other failures come through here + return HandlePacket_UNIMPLEMENTED(p); +} + +/* `T XX' -- status of thread + Check if the specified thread is alive. + The thread number is in hex? */ + +rnb_err_t +RNBRemote::HandlePacket_T (const char *p) +{ + p++; + if (p == NULL || *p == '\0') + { + return HandlePacket_ILLFORMED ("No thread specified in T packet"); + } + if (!m_ctx.HasValidProcessID()) + { + return SendPacket ("E15"); + } + errno = 0; + nub_thread_t tid = strtoul (p, NULL, 16); + if (errno != 0 && tid == 0) + { + return HandlePacket_ILLFORMED ("Could not parse thread number in T packet"); + } + + nub_state_t state = DNBThreadGetState (m_ctx.ProcessID(), tid); + if (state == eStateInvalid || state == eStateExited || state == eStateCrashed) + { + return SendPacket ("E16"); + } + + return SendPacket ("OK"); +} + + +rnb_err_t +RNBRemote::HandlePacket_z (const char *p) +{ + if (p == NULL || *p == '\0') + return HandlePacket_ILLFORMED ("No thread specified in z packet"); + + if (!m_ctx.HasValidProcessID()) + return SendPacket ("E15"); + + char packet_cmd = *p++; + char break_type = *p++; + + if (*p++ != ',') + return HandlePacket_ILLFORMED ("Comma separator missing in z packet"); + + char *c = NULL; + nub_process_t pid = m_ctx.ProcessID(); + errno = 0; + nub_addr_t addr = strtoull (p, &c, 16); + if (errno != 0 && addr == 0) + return HandlePacket_ILLFORMED ("Invalid address in z packet"); + p = c; + if (*p++ != ',') + return HandlePacket_ILLFORMED ("Comma separator missing in z packet"); + + errno = 0; + uint32_t byte_size = strtoul (p, &c, 16); + if (errno != 0 && byte_size == 0) + return HandlePacket_ILLFORMED ("Invalid length in z packet"); + + if (packet_cmd == 'Z') + { + // set + switch (break_type) + { + case '0': // set software breakpoint + case '1': // set hardware breakpoint + { + // gdb can send multiple Z packets for the same address and + // these calls must be ref counted. + bool hardware = (break_type == '1'); + + // Check if we currently have a breakpoint already set at this address + BreakpointMapIter pos = m_breakpoints.find(addr); + if (pos != m_breakpoints.end()) + { + // We do already have a breakpoint at this address, increment + // its reference count and return OK + pos->second.Retain(); + return SendPacket ("OK"); + } + else + { + // We do NOT already have a breakpoint at this address, So lets + // create one. + nub_break_t break_id = DNBBreakpointSet (pid, addr, byte_size, hardware); + if (break_id != INVALID_NUB_BREAK_ID) + { + // We successfully created a breakpoint, now lets full out + // a ref count structure with the breakID and add it to our + // map. + Breakpoint rnbBreakpoint(break_id); + m_breakpoints[addr] = rnbBreakpoint; + return SendPacket ("OK"); + } + else + { + // We failed to set the software breakpoint + return SendPacket ("E09"); + } + } + } + break; + + case '2': // set write watchpoint + case '3': // set read watchpoint + case '4': // set access watchpoint + { + bool hardware = true; + uint32_t watch_flags = 0; + if (break_type == '2') + watch_flags = WATCH_TYPE_WRITE; + else if (break_type == '3') + watch_flags = WATCH_TYPE_READ; + else + watch_flags = WATCH_TYPE_READ | WATCH_TYPE_WRITE; + + // Check if we currently have a watchpoint already set at this address + BreakpointMapIter pos = m_watchpoints.find(addr); + if (pos != m_watchpoints.end()) + { + // We do already have a watchpoint at this address, increment + // its reference count and return OK + pos->second.Retain(); + return SendPacket ("OK"); + } + else + { + // We do NOT already have a breakpoint at this address, So lets + // create one. + nub_watch_t watch_id = DNBWatchpointSet (pid, addr, byte_size, watch_flags, hardware); + if (watch_id != INVALID_NUB_BREAK_ID) + { + // We successfully created a watchpoint, now lets full out + // a ref count structure with the watch_id and add it to our + // map. + Breakpoint rnbWatchpoint(watch_id); + m_watchpoints[addr] = rnbWatchpoint; + return SendPacket ("OK"); + } + else + { + // We failed to set the watchpoint + return SendPacket ("E09"); + } + } + } + break; + + default: + break; + } + } + else if (packet_cmd == 'z') + { + // remove + switch (break_type) + { + case '0': // remove software breakpoint + case '1': // remove hardware breakpoint + { + // gdb can send multiple z packets for the same address and + // these calls must be ref counted. + BreakpointMapIter pos = m_breakpoints.find(addr); + if (pos != m_breakpoints.end()) + { + // We currently have a breakpoint at address ADDR. Decrement + // its reference count, and it that count is now zero we + // can clear the breakpoint. + pos->second.Release(); + if (pos->second.RefCount() == 0) + { + if (DNBBreakpointClear (pid, pos->second.BreakID())) + { + m_breakpoints.erase(pos); + return SendPacket ("OK"); + } + else + { + return SendPacket ("E08"); + } + } + else + { + // We still have references to this breakpoint don't + // delete it, just decrementing the reference count + // is enough. + return SendPacket ("OK"); + } + } + else + { + // We don't know about any breakpoints at this address + return SendPacket ("E08"); + } + } + break; + + case '2': // remove write watchpoint + case '3': // remove read watchpoint + case '4': // remove access watchpoint + { + // gdb can send multiple z packets for the same address and + // these calls must be ref counted. + BreakpointMapIter pos = m_watchpoints.find(addr); + if (pos != m_watchpoints.end()) + { + // We currently have a watchpoint at address ADDR. Decrement + // its reference count, and it that count is now zero we + // can clear the watchpoint. + pos->second.Release(); + if (pos->second.RefCount() == 0) + { + if (DNBWatchpointClear (pid, pos->second.BreakID())) + { + m_watchpoints.erase(pos); + return SendPacket ("OK"); + } + else + { + return SendPacket ("E08"); + } + } + else + { + // We still have references to this watchpoint don't + // delete it, just decrementing the reference count + // is enough. + return SendPacket ("OK"); + } + } + else + { + // We don't know about any watchpoints at this address + return SendPacket ("E08"); + } + } + break; + + default: + break; + } + } + return HandlePacket_UNIMPLEMENTED(p); +} + +/* `p XX' + print the contents of register X */ + +rnb_err_t +RNBRemote::HandlePacket_p (const char *p) +{ + if (p == NULL || *p == '\0') + { + return HandlePacket_ILLFORMED ("No thread specified in p packet"); + } + if (!m_ctx.HasValidProcessID()) + { + return SendPacket ("E15"); + } + nub_process_t pid = m_ctx.ProcessID(); + errno = 0; + uint32_t reg = strtoul (p + 1, NULL, 16); + if (errno != 0 && reg == 0) + { + return HandlePacket_ILLFORMED ("Could not parse thread number in p packet"); + } + + const register_map_entry_t *reg_entry; + + if (reg < g_num_reg_entries) + reg_entry = &g_reg_entries[reg]; + else + reg_entry = NULL; + + std::ostringstream ostrm; + if (reg_entry == NULL) + { + DNBLogError("RNBRemote::HandlePacket_p(%s): unknown register number %u requested\n", p, reg); + ostrm << "00000000"; + } + else if (reg_entry->nub_info.reg == -1) + { + if (reg_entry->gdb_size > 0) + { + if (reg_entry->fail_value != NULL) + { + append_hex_value(ostrm, reg_entry->fail_value, reg_entry->gdb_size, false); + } + else + { + std::basic_string<uint8_t> zeros(reg_entry->gdb_size, '\0'); + append_hex_value(ostrm, zeros.data(), zeros.size(), false); + } + } + } + else + { + nub_thread_t tid = GetCurrentThread(); + register_value_in_hex_fixed_width (ostrm, pid, tid, reg_entry); + } + return SendPacket (ostrm.str()); +} + +/* `Pnn=rrrrr' + Set register number n to value r. + n and r are hex strings. */ + +rnb_err_t +RNBRemote::HandlePacket_P (const char *p) +{ + if (p == NULL || *p == '\0') + { + return HandlePacket_ILLFORMED ("Empty P packet"); + } + if (!m_ctx.HasValidProcessID()) + { + return SendPacket ("E28"); + } + + nub_process_t pid = m_ctx.ProcessID(); + + StringExtractor packet (p); + + const char cmd_char = packet.GetChar(); + // Register ID is always in big endian + const uint32_t reg = packet.GetHexMaxU32 (false, UINT32_MAX); + const char equal_char = packet.GetChar(); + + if (cmd_char != 'P') + return HandlePacket_ILLFORMED ("Improperly formed P packet"); + + if (reg == UINT32_MAX) + return SendPacket ("E29"); + + if (equal_char != '=') + return SendPacket ("E30"); + + const register_map_entry_t *reg_entry; + + if (reg >= g_num_reg_entries) + return SendPacket("E47"); + + reg_entry = &g_reg_entries[reg]; + + if (reg_entry->nub_info.set == -1 && reg_entry->nub_info.reg == -1) + { + DNBLogError("RNBRemote::HandlePacket_P(%s): unknown register number %u requested\n", p, reg); + return SendPacket("E48"); + } + + DNBRegisterValue reg_value; + reg_value.info = reg_entry->nub_info; + packet.GetHexBytes (reg_value.value.v_sint8, reg_entry->gdb_size, 0xcc); + + nub_thread_t tid; + tid = GetCurrentThread (); + + if (!DNBThreadSetRegisterValueByID (pid, tid, reg_entry->nub_info.set, reg_entry->nub_info.reg, ®_value)) + { + return SendPacket ("E32"); + } + return SendPacket ("OK"); +} + +/* `c [addr]' + Continue, optionally from a specified address. */ + +rnb_err_t +RNBRemote::HandlePacket_c (const char *p) +{ + const nub_process_t pid = m_ctx.ProcessID(); + + if (pid == INVALID_NUB_PROCESS) + return SendPacket ("E23"); + + DNBThreadResumeAction action = { INVALID_NUB_THREAD, eStateRunning, 0, INVALID_NUB_ADDRESS }; + + if (*(p + 1) != '\0') + { + action.tid = GetContinueThread(); + errno = 0; + action.addr = strtoull (p + 1, NULL, 16); + if (errno != 0 && action.addr == 0) + return HandlePacket_ILLFORMED ("Could not parse address in c packet"); + } + + DNBThreadResumeActions thread_actions; + thread_actions.Append(action); + thread_actions.SetDefaultThreadActionIfNeeded(eStateRunning, 0); + if (!DNBProcessResume (pid, thread_actions.GetFirst(), thread_actions.GetSize())) + return SendPacket ("E25"); + // Don't send an "OK" packet; response is the stopped/exited message. + return rnb_success; +} + +/* `C sig [;addr]' + Resume with signal sig, optionally at address addr. */ + +rnb_err_t +RNBRemote::HandlePacket_C (const char *p) +{ + const nub_process_t pid = m_ctx.ProcessID(); + + if (pid == INVALID_NUB_PROCESS) + return SendPacket ("E36"); + + DNBThreadResumeAction action = { INVALID_NUB_THREAD, eStateRunning, 0, INVALID_NUB_ADDRESS }; + int process_signo = -1; + if (*(p + 1) != '\0') + { + action.tid = GetContinueThread(); + char *end = NULL; + errno = 0; + process_signo = strtoul (p + 1, &end, 16); + if (errno != 0) + return HandlePacket_ILLFORMED ("Could not parse signal in C packet"); + else if (*end == ';') + { + errno = 0; + action.addr = strtoull (end + 1, NULL, 16); + if (errno != 0 && action.addr == 0) + return HandlePacket_ILLFORMED ("Could not parse address in C packet"); + } + } + + DNBThreadResumeActions thread_actions; + thread_actions.Append (action); + thread_actions.SetDefaultThreadActionIfNeeded (eStateRunning, action.signal); + if (!DNBProcessSignal(pid, process_signo)) + return SendPacket ("E52"); + if (!DNBProcessResume (pid, thread_actions.GetFirst(), thread_actions.GetSize())) + return SendPacket ("E38"); + /* Don't send an "OK" packet; response is the stopped/exited message. */ + return rnb_success; +} + +//---------------------------------------------------------------------- +// 'D' packet +// Detach from gdb. +//---------------------------------------------------------------------- +rnb_err_t +RNBRemote::HandlePacket_D (const char *p) +{ + SendPacket ("OK"); + if (m_ctx.HasValidProcessID()) + DNBProcessDetach(m_ctx.ProcessID()); + return rnb_success; +} + +/* `k' + Kill the inferior process. */ + +rnb_err_t +RNBRemote::HandlePacket_k (const char *p) +{ + if (!m_ctx.HasValidProcessID()) + return SendPacket ("E26"); + if (!DNBProcessKill (m_ctx.ProcessID())) + return SendPacket ("E27"); + return SendPacket ("OK"); +} + +rnb_err_t +RNBRemote::HandlePacket_stop_process (const char *p) +{ + DNBProcessSignal (m_ctx.ProcessID(), SIGSTOP); + //DNBProcessSignal (m_ctx.ProcessID(), SIGINT); + // Do not send any response packet! Wait for the stop reply packet to naturally happen + return rnb_success; +} + +/* `s' + Step the inferior process. */ + +rnb_err_t +RNBRemote::HandlePacket_s (const char *p) +{ + const nub_process_t pid = m_ctx.ProcessID(); + if (pid == INVALID_NUB_PROCESS) + return SendPacket ("E32"); + + // Hardware supported stepping not supported on arm + nub_thread_t tid = GetContinueThread (); + if (tid == 0 || tid == -1) + tid = GetCurrentThread(); + + if (tid == INVALID_NUB_THREAD) + return SendPacket ("E33"); + + DNBThreadResumeActions thread_actions; + thread_actions.AppendAction(tid, eStateStepping); + + // Make all other threads stop when we are stepping + thread_actions.SetDefaultThreadActionIfNeeded (eStateStopped, 0); + if (!DNBProcessResume (pid, thread_actions.GetFirst(), thread_actions.GetSize())) + return SendPacket ("E49"); + // Don't send an "OK" packet; response is the stopped/exited message. + return rnb_success; +} + +/* `S sig [;addr]' + Step with signal sig, optionally at address addr. */ + +rnb_err_t +RNBRemote::HandlePacket_S (const char *p) +{ + const nub_process_t pid = m_ctx.ProcessID(); + if (pid == INVALID_NUB_PROCESS) + return SendPacket ("E36"); + + DNBThreadResumeAction action = { INVALID_NUB_THREAD, eStateStepping, 0, INVALID_NUB_ADDRESS }; + + if (*(p + 1) != '\0') + { + char *end = NULL; + errno = 0; + action.signal = strtoul (p + 1, &end, 16); + if (errno != 0) + return HandlePacket_ILLFORMED ("Could not parse signal in S packet"); + else if (*end == ';') + { + errno = 0; + action.addr = strtoull (end + 1, NULL, 16); + if (errno != 0 && action.addr == 0) + { + return HandlePacket_ILLFORMED ("Could not parse address in S packet"); + } + } + } + + action.tid = GetContinueThread (); + if (action.tid == 0 || action.tid == -1) + return SendPacket ("E40"); + + nub_state_t tstate = DNBThreadGetState (pid, action.tid); + if (tstate == eStateInvalid || tstate == eStateExited) + return SendPacket ("E37"); + + + DNBThreadResumeActions thread_actions; + thread_actions.Append (action); + + // Make all other threads stop when we are stepping + thread_actions.SetDefaultThreadActionIfNeeded(eStateStopped, 0); + if (!DNBProcessResume (pid, thread_actions.GetFirst(), thread_actions.GetSize())) + return SendPacket ("E39"); + + // Don't send an "OK" packet; response is the stopped/exited message. + return rnb_success; +} + +rnb_err_t +RNBRemote::HandlePacket_qHostInfo (const char *p) +{ + std::ostringstream strm; + + uint32_t cputype, is_64_bit_capable; + size_t len = sizeof(cputype); + bool promoted_to_64 = false; + if (::sysctlbyname("hw.cputype", &cputype, &len, NULL, 0) == 0) + { + len = sizeof (is_64_bit_capable); + if (::sysctlbyname("hw.cpu64bit_capable", &is_64_bit_capable, &len, NULL, 0) == 0) + { + if (is_64_bit_capable && ((cputype & CPU_ARCH_ABI64) == 0)) + { + promoted_to_64 = true; + cputype |= CPU_ARCH_ABI64; + } + } + + strm << "cputype:" << std::dec << cputype << ';'; + } + + uint32_t cpusubtype; + len = sizeof(cpusubtype); + if (::sysctlbyname("hw.cpusubtype", &cpusubtype, &len, NULL, 0) == 0) + { + if (promoted_to_64 && + cputype == CPU_TYPE_X86_64 && + cpusubtype == CPU_SUBTYPE_486) + cpusubtype = CPU_SUBTYPE_X86_64_ALL; + + strm << "cpusubtype:" << std::dec << cpusubtype << ';'; + } + + char ostype[64]; + len = sizeof(ostype); + if (::sysctlbyname("kern.ostype", &ostype, &len, NULL, 0) == 0) + strm << "ostype:" << std::dec << ostype << ';'; + + strm << "vendor:apple;"; + +#if defined (__LITTLE_ENDIAN__) + strm << "endian:little;"; +#elif defined (__BIG_ENDIAN__) + strm << "endian:big;"; +#elif defined (__PDP_ENDIAN__) + strm << "endian:pdp;"; +#endif + + strm << "ptrsize:" << std::dec << sizeof(void *) << ';'; + return SendPacket (strm.str()); +} + diff --git a/lldb/tools/debugserver/source/RNBRemote.h b/lldb/tools/debugserver/source/RNBRemote.h new file mode 100644 index 00000000000..bec9d1812d0 --- /dev/null +++ b/lldb/tools/debugserver/source/RNBRemote.h @@ -0,0 +1,309 @@ +//===-- RNBRemote.h ---------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 12/12/07. +// +//===----------------------------------------------------------------------===// + +#ifndef __RNBRemote_h__ +#define __RNBRemote_h__ + +#include "RNBDefs.h" +#include "DNB.h" +#include "RNBContext.h" +#include "RNBSocket.h" +#include "PThreadMutex.h" +#include <string> +#include <vector> +#include <deque> +#include <map> + +class RNBSocket; +class RNBContext; +class PThreadEvents; + +enum event_loop_mode { debug_nub, gdb_remote_protocol, done }; + +class RNBRemote +{ +public: + + typedef enum { + invalid_packet = 0, + ack, // '+' + nack, // '-' + halt, // ^C (async halt) + use_extended_mode, // '!' + why_halted, // '?' + set_argv, // 'A' + set_bp, // 'B' + cont, // 'c' + continue_with_sig, // 'C' + detach, // 'D' + read_general_regs, // 'g' + write_general_regs, // 'G' + set_thread, // 'H' + step_inferior_one_cycle, // 'i' + signal_and_step_inf_one_cycle, // 'I' + kill, // 'k' + read_memory, // 'm' + write_memory, // 'M' + read_register, // 'p' + write_register, // 'P' + restart, // 'R' + single_step, // 's' + single_step_with_sig, // 'S' + search_mem_backwards, // 't' + thread_alive_p, // 'T' + vattach, // 'vAttach' + vattachwait, // 'vAttachWait' + vcont, // 'vCont' + vcont_list_actions, // 'vCont?' + write_data_to_memory, // 'X' + insert_mem_bp, // 'Z0' + remove_mem_bp, // 'z0' + insert_hardware_bp, // 'Z1' + remove_hardware_bp, // 'z1' + insert_write_watch_bp, // 'Z2' + remove_write_watch_bp, // 'z2' + insert_read_watch_bp, // 'Z3' + remove_read_watch_bp, // 'z3' + insert_access_watch_bp, // 'Z4' + remove_access_watch_bp, // 'z4' + + query_current_thread_id, // 'qC' + query_memory_crc, // 'qCRC:' + query_thread_ids_first, // 'qfThreadInfo' + query_thread_ids_subsequent, // 'qsThreadInfo' + query_thread_extra_info, // 'qThreadExtraInfo' + query_thread_stop_info, // 'qThreadStopInfo' + query_image_offsets, // 'qOffsets' + query_symbol_lookup, // 'gSymbols' + query_launch_success, // 'qLaunchSuccess' + query_register_info, // 'qRegisterInfo' + query_shlib_notify_info_addr, // 'qShlibInfoAddr' + query_step_packet_supported, // 'qStepPacketSupported' + query_host_info, // 'qHostInfo' + pass_signals_to_inferior, // 'QPassSignals' + start_noack_mode, // 'QStartNoAckMode' + set_logging_mode, // 'QSetLogging:' + set_max_packet_size, // 'QSetMaxPacketSize:' + set_max_payload_size, // 'QSetMaxPayloadSize:' + set_environment_variable, // 'QEnvironment:' + allocate_memory, // '_M' + deallocate_memory, // '_m' + + unknown_type, + } PacketEnum; + + typedef rnb_err_t (RNBRemote::*HandlePacketCallback)(const char *p); + + RNBRemote(bool use_native_regs); + ~RNBRemote(); + + static void InitializeRegisters (int use_native); + + rnb_err_t HandleAsyncPacket(PacketEnum *type = NULL); + rnb_err_t HandleReceivedPacket(PacketEnum *type = NULL); + + nub_thread_t GetContinueThread () const + { + return m_continue_thread; + } + + void SetContinueThread (nub_thread_t tid) + { + m_continue_thread = tid; + } + + nub_thread_t GetCurrentThread () const + { + if (m_thread == 0 || m_thread == -1) + return DNBProcessGetCurrentThread (m_ctx.ProcessID()); + return m_thread; + } + + void SetCurrentThread (nub_thread_t tid) + { + DNBProcessSetCurrentThread (m_ctx.ProcessID(), tid); + m_thread = tid; + } + + static void* ThreadFunctionReadRemoteData(void *arg); + void StartReadRemoteDataThread (); + void StopReadRemoteDataThread (); + + void NotifyThatProcessStopped (void); + + rnb_err_t HandlePacket_A (const char *p); + rnb_err_t HandlePacket_H (const char *p); + rnb_err_t HandlePacket_qC (const char *p); + rnb_err_t HandlePacket_qLaunchSuccess (const char *p); + rnb_err_t HandlePacket_qRegisterInfo (const char *p); + rnb_err_t HandlePacket_qShlibInfoAddr (const char *p); + rnb_err_t HandlePacket_qStepPacketSupported (const char *p); + rnb_err_t HandlePacket_qThreadInfo (const char *p); + rnb_err_t HandlePacket_qThreadExtraInfo (const char *p); + rnb_err_t HandlePacket_qThreadStopInfo (const char *p); + rnb_err_t HandlePacket_qHostInfo (const char *p); + rnb_err_t HandlePacket_Q (const char *p); + rnb_err_t HandlePacket_last_signal (const char *p); + rnb_err_t HandlePacket_m (const char *p); + rnb_err_t HandlePacket_M (const char *p); + rnb_err_t HandlePacket_X (const char *p); + rnb_err_t HandlePacket_g (const char *p); + rnb_err_t HandlePacket_G (const char *p); + rnb_err_t HandlePacket_z (const char *p); + rnb_err_t HandlePacket_T (const char *p); + rnb_err_t HandlePacket_p (const char *p); + rnb_err_t HandlePacket_P (const char *p); + rnb_err_t HandlePacket_c (const char *p); + rnb_err_t HandlePacket_C (const char *p); + rnb_err_t HandlePacket_D (const char *p); + rnb_err_t HandlePacket_k (const char *p); + rnb_err_t HandlePacket_s (const char *p); + rnb_err_t HandlePacket_S (const char *p); + rnb_err_t HandlePacket_v (const char *p); + rnb_err_t HandlePacket_UNIMPLEMENTED (const char *p); + rnb_err_t HandlePacket_ILLFORMED (const char *description); + rnb_err_t HandlePacket_AllocateMemory (const char *p); + rnb_err_t HandlePacket_DeallocateMemory (const char *p); + + rnb_err_t HandlePacket_stop_process (const char *p); + + rnb_err_t SendStopReplyPacketForThread (nub_thread_t tid); + rnb_err_t SendHexEncodedBytePacket (const char *header, const void *buf, size_t buf_len, const char *footer); + rnb_err_t SendSTDOUTPacket (char *buf, nub_size_t buf_size); + rnb_err_t SendSTDERRPacket (char *buf, nub_size_t buf_size); + void FlushSTDIO (); + + RNBContext& Context() { return m_ctx; } + RNBSocket& Comm() { return m_comm; } + +private: + // Outlaw some contructors + RNBRemote (const RNBRemote &); + +protected: + + static void + InitializeNativeRegisters (); + + rnb_err_t GetCommData (); + void CommDataReceived(const std::string& data); + struct Packet + { + typedef std::vector<Packet> collection; + typedef collection::iterator iterator; + typedef collection::const_iterator const_iterator; + PacketEnum type; + HandlePacketCallback normal; // Function to call when inferior is halted + HandlePacketCallback async; // Function to call when inferior is running + std::string abbrev; + std::string printable_name; + Packet() : + type(invalid_packet), + normal (NULL), + async (NULL), + abbrev (), + printable_name () + { + } + + Packet( PacketEnum in_type, + HandlePacketCallback in_normal, + HandlePacketCallback in_async, + const char *in_abbrev, + const char *in_printable_name) : + type (in_type), + normal (in_normal), + async (in_async), + abbrev (in_abbrev), + printable_name (in_printable_name) + { + } + }; + + rnb_err_t GetPacket (std::string &packet_data, RNBRemote::Packet& packet_info, bool wait); + rnb_err_t SendPacket (const std::string &); + + void CreatePacketTable (); + rnb_err_t GetPacketPayload (std::string &); + + // gdb can send multiple Z/z packets for the same address and + // these calls must be ref counted. + typedef struct Breakpoint + { + Breakpoint(nub_break_t breakID) : + m_breakID(breakID), + m_refCount(1) + { + } + + Breakpoint() : + m_breakID(INVALID_NUB_BREAK_ID), + m_refCount(0) + { + } + + Breakpoint(const Breakpoint& rhs) : + m_breakID(rhs.m_breakID), + m_refCount(rhs.m_refCount) + { + } + + nub_break_t BreakID() const { return m_breakID; } + uint32_t RefCount() const { return m_refCount; } + void Release() { if (m_refCount > 0) --m_refCount; } + void Retain() { ++m_refCount; } + + nub_break_t m_breakID; + uint32_t m_refCount; + }; + typedef std::map<nub_addr_t, Breakpoint> BreakpointMap; + typedef BreakpointMap::iterator BreakpointMapIter; + typedef BreakpointMap::const_iterator BreakpointMapConstIter; + RNBContext m_ctx; // process context + RNBSocket m_comm; // communication port + bool m_extended_mode; // are we in extended mode? + bool m_noack_mode; // are we in no-ack mode? + nub_thread_t m_continue_thread; // thread to continue; 0 for any, -1 for all + nub_thread_t m_thread; // thread for other ops; 0 for any, -1 for all + PThreadMutex m_mutex; // Mutex that protects + uint32_t m_packets_recvd; + Packet::collection m_packets; + std::deque<std::string> m_rx_packets; + std::string m_rx_partial_data; // For packets that may come in more than one batch, anything left over can be left here + pthread_t m_rx_pthread; + BreakpointMap m_breakpoints; + BreakpointMap m_watchpoints; + uint32_t m_max_payload_size; // the maximum sized payload we should send to gdb + bool m_use_native_regs; +}; + +/* We translate the /usr/include/mach/exception_types.h exception types + (e.g. EXC_BAD_ACCESS) to the fake BSD signal numbers that gdb uses + in include/gdb/signals.h (e.g. TARGET_EXC_BAD_ACCESS). These hard + coded values for TARGET_EXC_BAD_ACCESS et al must match the gdb + values in its include/gdb/signals.h. */ + +#define TARGET_EXC_BAD_ACCESS 0x91 +#define TARGET_EXC_BAD_INSTRUCTION 0x92 +#define TARGET_EXC_ARITHMETIC 0x93 +#define TARGET_EXC_EMULATION 0x94 +#define TARGET_EXC_SOFTWARE 0x95 +#define TARGET_EXC_BREAKPOINT 0x96 + +/* Generally speaking, you can't assume gdb can receive more than 399 bytes + at a time with a random gdb. This bufsize constant is only specifying + how many bytes gdb can *receive* from debugserver -- it tells us nothing + about how many bytes gdb might try to send in a single packet. */ +#define DEFAULT_GDB_REMOTE_PROTOCOL_BUFSIZE 399 + +#endif // #ifndef __RNBRemote_h__ diff --git a/lldb/tools/debugserver/source/RNBServices.cpp b/lldb/tools/debugserver/source/RNBServices.cpp new file mode 100644 index 00000000000..1fe791d37be --- /dev/null +++ b/lldb/tools/debugserver/source/RNBServices.cpp @@ -0,0 +1,145 @@ +//===-- RNBServices.cpp -----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Christopher Friesen on 3/21/08. +// +//===----------------------------------------------------------------------===// + +#import "RNBServices.h" + +#import <CoreFoundation/CoreFoundation.h> +#import <unistd.h> +#import "DNBLog.h" + +#if defined (__arm__) +#import <SpringBoardServices/SpringBoardServices.h> +#endif + +int +ListApplications(std::string& plist, bool opt_runningApps, bool opt_debuggable) +{ +#if defined (__arm__) + int result = -1; + + CFAllocatorRef alloc = kCFAllocatorDefault; + + // Create a mutable array that we can populate. Specify zero so it can be of any size. + CFReleaser<CFMutableArrayRef> plistMutableArray (::CFArrayCreateMutable (alloc, 0, &kCFTypeArrayCallBacks)); + + CFReleaser<CFStringRef> sbsFrontAppID (::SBSCopyFrontmostApplicationDisplayIdentifier ()); + CFReleaser<CFArrayRef> sbsAppIDs (::SBSCopyApplicationDisplayIdentifiers (opt_runningApps, opt_debuggable)); + + CFIndex count = ::CFArrayGetCount (sbsAppIDs.get()); + CFIndex i = 0; + for (i = 0; i < count; i++) + { + CFStringRef displayIdentifier = (CFStringRef)::CFArrayGetValueAtIndex (sbsAppIDs.get(), i); + + // Create a new mutable dictionary for each application + CFReleaser<CFMutableDictionaryRef> appInfoDict (::CFDictionaryCreateMutable (alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); + + // Get the process id for the app (if there is one) + pid_t pid = INVALID_NUB_PROCESS; + if (::SBSProcessIDForDisplayIdentifier ((CFStringRef)displayIdentifier, &pid) == true) + { + CFReleaser<CFNumberRef> pidCFNumber (::CFNumberCreate (alloc, kCFNumberSInt32Type, &pid)); + ::CFDictionarySetValue (appInfoDict.get(), DTSERVICES_APP_PID_KEY, pidCFNumber.get()); + } + + // Set the a boolean to indicate if this is the front most + if (sbsFrontAppID.get() && displayIdentifier && (::CFStringCompare (sbsFrontAppID.get(), displayIdentifier, 0) == kCFCompareEqualTo)) + ::CFDictionarySetValue (appInfoDict.get(), DTSERVICES_APP_FRONTMOST_KEY, kCFBooleanTrue); + else + ::CFDictionarySetValue (appInfoDict.get(), DTSERVICES_APP_FRONTMOST_KEY, kCFBooleanFalse); + + + CFReleaser<CFStringRef> executablePath (::SBSCopyExecutablePathForDisplayIdentifier (displayIdentifier)); + if (executablePath.get() != NULL) + { + ::CFDictionarySetValue (appInfoDict.get(), DTSERVICES_APP_PATH_KEY, executablePath.get()); + } + + CFReleaser<CFStringRef> iconImagePath (::SBSCopyIconImagePathForDisplayIdentifier (displayIdentifier)) ; + if (iconImagePath.get() != NULL) + { + ::CFDictionarySetValue (appInfoDict.get(), DTSERVICES_APP_ICON_PATH_KEY, iconImagePath.get()); + } + + CFReleaser<CFStringRef> localizedDisplayName (::SBSCopyLocalizedApplicationNameForDisplayIdentifier (displayIdentifier)); + if (localizedDisplayName.get() != NULL) + { + ::CFDictionarySetValue (appInfoDict.get(), DTSERVICES_APP_DISPLAY_NAME_KEY, localizedDisplayName.get()); + } + + // Append the application info to the plist array + ::CFArrayAppendValue (plistMutableArray.get(), appInfoDict.get()); + } + + CFReleaser<CFDataRef> plistData (::CFPropertyListCreateXMLData (alloc, plistMutableArray.get())); + + // write plist to service port + if (plistData.get() != NULL) + { + CFIndex size = ::CFDataGetLength (plistData.get()); + const UInt8 *bytes = ::CFDataGetBytePtr (plistData.get()); + if (bytes != NULL && size > 0) + { + plist.assign((char *)bytes, size); + return 0; // Success + } + else + { + DNBLogError("empty application property list."); + result = -2; + } + } + else + { + DNBLogError("serializing task list."); + result = -3; + } + + return result; +#else + // TODO: list all current processes + DNBLogError("SBS doesn't support getting application list."); + return -1; +#endif +} + + +bool +IsSBProcess (nub_process_t pid) +{ +#if defined (__arm__) + bool opt_runningApps = true; + bool opt_debuggable = false; + + CFReleaser<CFArrayRef> sbsAppIDs (::SBSCopyApplicationDisplayIdentifiers (opt_runningApps, opt_debuggable)); + if (sbsAppIDs.get() != NULL) + { + CFIndex count = ::CFArrayGetCount (sbsAppIDs.get()); + CFIndex i = 0; + for (i = 0; i < count; i++) + { + CFStringRef displayIdentifier = (CFStringRef)::CFArrayGetValueAtIndex (sbsAppIDs.get(), i); + + // Get the process id for the app (if there is one) + pid_t sbs_pid = INVALID_NUB_PROCESS; + if (::SBSProcessIDForDisplayIdentifier ((CFStringRef)displayIdentifier, &sbs_pid) == TRUE) + { + if (sbs_pid == pid) + return true; + } + } + } +#endif + return false; +} + diff --git a/lldb/tools/debugserver/source/RNBServices.h b/lldb/tools/debugserver/source/RNBServices.h new file mode 100644 index 00000000000..0164e33b0bf --- /dev/null +++ b/lldb/tools/debugserver/source/RNBServices.h @@ -0,0 +1,29 @@ +//===-- RNBServices.h -------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Christopher Friesen on 3/21/08. +// +//===----------------------------------------------------------------------===// + +#ifndef __RNBServices_h__ +#define __RNBServices_h__ + +#include <string> +#include "RNBDefs.h" + +#define DTSERVICES_APP_FRONTMOST_KEY CFSTR("isFrontApp") +#define DTSERVICES_APP_PATH_KEY CFSTR("executablePath") +#define DTSERVICES_APP_ICON_PATH_KEY CFSTR("iconPath") +#define DTSERVICES_APP_DISPLAY_NAME_KEY CFSTR("displayName") +#define DTSERVICES_APP_PID_KEY CFSTR("pid") + +int ListApplications (std::string &plist, bool opt_runningApps, bool opt_debuggable); +bool IsSBProcess (nub_process_t pid); + +#endif // __RNBServices_h__ diff --git a/lldb/tools/debugserver/source/RNBSocket.cpp b/lldb/tools/debugserver/source/RNBSocket.cpp new file mode 100644 index 00000000000..08fa4ac79ce --- /dev/null +++ b/lldb/tools/debugserver/source/RNBSocket.cpp @@ -0,0 +1,251 @@ +//===-- RNBSocket.cpp -------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 12/12/07. +// +//===----------------------------------------------------------------------===// + +#include "RNBSocket.h" +#include <errno.h> +#include <fcntl.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <termios.h> +#include "DNBLog.h" +#include "DNBError.h" + +#if defined (__arm__) +#include "lockdown.h" +#endif + +/* Once we have a RNBSocket object with a port # specified, + this function is called to wait for an incoming connection. + This function blocks while waiting for that connection. */ + +rnb_err_t +RNBSocket::Listen (in_port_t listen_port_num) +{ + //DNBLogThreadedIf(LOG_RNB_COMM, "%8u RNBSocket::%s called", (uint32_t)m_timer.ElapsedMicroSeconds(true), __FUNCTION__); + // Disconnect without saving errno + Disconnect (false); + + DNBError err; + int listen_port = ::socket (AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (listen_port == -1) + err.SetError(errno, DNBError::POSIX); + + if (err.Fail() || DNBLogCheckLogBit(LOG_RNB_COMM)) + err.LogThreaded("::socket ( domain = AF_INET, type = SOCK_STREAM, protocol = IPPROTO_TCP ) => socket = %i", listen_port); + + if (err.Fail()) + return rnb_err; + + // enable local address reuse + SetSocketOption (listen_port, SOL_SOCKET, SO_REUSEADDR, 1); + + struct sockaddr_in sa; + ::memset (&sa, 0, sizeof sa); + sa.sin_len = sizeof sa; + sa.sin_family = AF_INET; + sa.sin_port = htons (listen_port_num); + sa.sin_addr.s_addr = htonl (INADDR_ANY); + + int error = ::bind (listen_port, (struct sockaddr *) &sa, sizeof(sa)); + if (error == -1) + err.SetError(errno, DNBError::POSIX); + + if (err.Fail() || DNBLogCheckLogBit(LOG_RNB_COMM)) + err.LogThreaded("::bind ( socket = %i, (struct sockaddr *) &sa, sizeof(sa)) )", listen_port); + + if (err.Fail()) + { + ClosePort (listen_port, false); + return rnb_err; + } + + error = ::listen (listen_port, 1); + if (error == -1) + err.SetError(errno, DNBError::POSIX); + + if (err.Fail() || DNBLogCheckLogBit(LOG_RNB_COMM)) + err.LogThreaded("::listen ( socket = %i, backlog = 1 )", listen_port); + + if (err.Fail()) + { + ClosePort (listen_port, false); + return rnb_err; + } + + m_conn_port = ::accept (listen_port, NULL, 0); + if (m_conn_port == -1) + err.SetError(errno, DNBError::POSIX); + + if (err.Fail() || DNBLogCheckLogBit(LOG_RNB_COMM)) + err.LogThreaded("::accept ( socket = %i, address = NULL, address_len = 0 )", listen_port); + + if (err.Fail()) + { + ClosePort (listen_port, false); + return rnb_err; + } + else + { + // We are done with the listen port + ClosePort (listen_port, false); + + // Keep our TCP packets coming without any delays. + SetSocketOption (m_conn_port, IPPROTO_TCP, TCP_NODELAY, 1); + } + + return rnb_success; +} + +#if defined (__arm__) +rnb_err_t +RNBSocket::ConnectToService() +{ + DNBLog("Connecting to com.apple.%s service...", DEBUGSERVER_PROGRAM_NAME); + // Disconnect from any previous connections + Disconnect(false); + + m_conn_port = ::lockdown_checkin (NULL, NULL); + if (m_conn_port == -1) + { + DNBLogThreadedIf(LOG_RNB_COMM, "::lockdown_checkin(NULL, NULL) failed"); + return rnb_not_connected; + } + m_conn_port_from_lockdown = true; + return rnb_success; +} +#endif + +rnb_err_t +RNBSocket::OpenFile (const char *path) +{ + DNBError err; + m_conn_port = open (path, O_RDWR); + if (m_conn_port == -1) + { + err.SetError(errno, DNBError::POSIX); + err.LogThreaded ("can't open file '%s'", path); + return rnb_not_connected; + } + else + { + struct termios stdin_termios; + + if (::tcgetattr (m_conn_port, &stdin_termios) == 0) + { + stdin_termios.c_lflag &= ~ECHO; // Turn off echoing + stdin_termios.c_lflag &= ~ICANON; // Get one char at a time + ::tcsetattr (m_conn_port, TCSANOW, &stdin_termios); + } + } + return rnb_success; +} + +int +RNBSocket::SetSocketOption(int fd, int level, int option_name, int option_value) +{ + return ::setsockopt(fd, level, option_name, &option_value, sizeof(option_value)); +} + +rnb_err_t +RNBSocket::Disconnect (bool save_errno) +{ + if (m_conn_port_from_lockdown) + m_conn_port_from_lockdown = false; + return ClosePort (m_conn_port, save_errno); +} + + +rnb_err_t +RNBSocket::Read (std::string &p) +{ + char buf[1024]; + p.clear(); + + // Note that BUF is on the stack so we must be careful to keep any + // writes to BUF from overflowing or we'll have security issues. + + if (m_conn_port == -1) + return rnb_err; + + //DNBLogThreadedIf(LOG_RNB_COMM, "%8u RNBSocket::%s calling read()", (uint32_t)m_timer.ElapsedMicroSeconds(true), __FUNCTION__); + DNBError err; + int bytesread = read (m_conn_port, buf, sizeof (buf)); + if (bytesread <= 0) + err.SetError(errno, DNBError::POSIX); + else + p.append(buf, bytesread); + + if (err.Fail() || DNBLogCheckLogBit(LOG_RNB_COMM)) + err.LogThreaded("::read ( %i, %p, %zu ) => %i", m_conn_port, buf, sizeof (buf), bytesread); + + // Our port went away - we have to mark this so IsConnected will return the truth. + if (bytesread == 0) + { + m_conn_port = -1; + return rnb_not_connected; + } + else if (bytesread == -1) + { + m_conn_port = -1; + return rnb_err; + } + // Strip spaces from the end of the buffer + while (!p.empty() && isspace (p[p.size() - 1])) + p.erase (p.size () - 1); + + // Most data in the debugserver packets valid printable characters... + DNBLogThreadedIf(LOG_RNB_COMM, "read: %s", p.c_str()); + return rnb_success; +} + +rnb_err_t +RNBSocket::Write (const void *buffer, size_t length) +{ + if (m_conn_port == -1) + return rnb_err; + + DNBError err; + int bytessent = send (m_conn_port, buffer, length, 0); + if (bytessent < 0) + err.SetError(errno, DNBError::POSIX); + + if (err.Fail() || DNBLogCheckLogBit(LOG_RNB_COMM)) + err.LogThreaded("::send ( socket = %i, buffer = %p, length = %zu, flags = 0 ) => %i", m_conn_port, buffer, length, bytessent); + + if (bytessent < 0) + return rnb_err; + + if (bytessent != length) + return rnb_err; + + DNBLogThreadedIf(LOG_RNB_PACKETS, "putpkt: %*s", length, (char *)buffer); // All data is string based in debugserver, so this is safe + DNBLogThreadedIf(LOG_RNB_COMM, "sent: %*s", length, (char *)buffer); + + return rnb_success; +} + + +rnb_err_t +RNBSocket::ClosePort (int& fd, bool save_errno) +{ + int close_err = 0; + if (fd > 0) + { + errno = 0; + close_err = close (fd); + fd = -1; + } + return close_err != 0 ? rnb_err : rnb_success; +} + + diff --git a/lldb/tools/debugserver/source/RNBSocket.h b/lldb/tools/debugserver/source/RNBSocket.h new file mode 100644 index 00000000000..de3db806ded --- /dev/null +++ b/lldb/tools/debugserver/source/RNBSocket.h @@ -0,0 +1,65 @@ +//===-- RNBSocket.h ---------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 12/12/07. +// +//===----------------------------------------------------------------------===// + +#ifndef __RNBSocket_h__ +#define __RNBSocket_h__ + +#include "RNBDefs.h" +#include <sys/socket.h> +#include <sys/types.h> +#include <string> +#include "DNBTimer.h" + +class RNBSocket +{ +public: + + RNBSocket () : + m_conn_port (-1), + m_conn_port_from_lockdown (false), + m_timer (true) // Make a thread safe timer + { + } + ~RNBSocket (void) + { + Disconnect (false); + } + + rnb_err_t Listen (in_port_t listen_port_num); +#if defined (__arm__) + rnb_err_t ConnectToService(); +#endif + rnb_err_t OpenFile (const char *path); + rnb_err_t Disconnect (bool save_errno); + rnb_err_t Read (std::string &p); + rnb_err_t Write (const void *buffer, size_t length); + + bool IsConnected () const { return m_conn_port != -1; } + void SaveErrno (int curr_errno); + DNBTimer& Timer() { return m_timer; } + + static int SetSocketOption(int fd, int level, int option_name, int option_value); +private: + // Outlaw some constructors + RNBSocket (const RNBSocket &); + +protected: + rnb_err_t ClosePort (int& fd, bool save_errno); + + int m_conn_port; // Socket we use to communicate once conn established + bool m_conn_port_from_lockdown; + DNBTimer m_timer; +}; + + +#endif // #ifndef __RNBSocket_h__ diff --git a/lldb/tools/debugserver/source/SysSignal.cpp b/lldb/tools/debugserver/source/SysSignal.cpp new file mode 100644 index 00000000000..69f34ed605c --- /dev/null +++ b/lldb/tools/debugserver/source/SysSignal.cpp @@ -0,0 +1,66 @@ +//===-- SysSignal.cpp -------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/18/07. +// +//===----------------------------------------------------------------------===// + +#include "SysSignal.h" +#include <signal.h> +#include <stddef.h> + +const char * +SysSignal::Name(int signal) +{ + switch (signal) + { + case SIGHUP: return "SIGHUP"; // 1 hangup + case SIGINT: return "SIGINT"; // 2 interrupt + case SIGQUIT: return "SIGQUIT"; // 3 quit + case SIGILL: return "SIGILL"; // 4 illegal instruction (not reset when caught) + case SIGTRAP: return "SIGTRAP"; // 5 trace trap (not reset when caught) + case SIGABRT: return "SIGABRT"; // 6 abort() +#if defined(_POSIX_C_SOURCE) + case SIGPOLL: return "SIGPOLL"; // 7 pollable event ([XSR] generated, not supported) +#else // !_POSIX_C_SOURCE + case SIGEMT: return "SIGEMT"; // 7 EMT instruction +#endif // !_POSIX_C_SOURCE + case SIGFPE: return "SIGFPE"; // 8 floating point exception + case SIGKILL: return "SIGKILL"; // 9 kill (cannot be caught or ignored) + case SIGBUS: return "SIGBUS"; // 10 bus error + case SIGSEGV: return "SIGSEGV"; // 11 segmentation violation + case SIGSYS: return "SIGSYS"; // 12 bad argument to system call + case SIGPIPE: return "SIGPIPE"; // 13 write on a pipe with no one to read it + case SIGALRM: return "SIGALRM"; // 14 alarm clock + case SIGTERM: return "SIGTERM"; // 15 software termination signal from kill + case SIGURG: return "SIGURG"; // 16 urgent condition on IO channel + case SIGSTOP: return "SIGSTOP"; // 17 sendable stop signal not from tty + case SIGTSTP: return "SIGTSTP"; // 18 stop signal from tty + case SIGCONT: return "SIGCONT"; // 19 continue a stopped process + case SIGCHLD: return "SIGCHLD"; // 20 to parent on child stop or exit + case SIGTTIN: return "SIGTTIN"; // 21 to readers pgrp upon background tty read + case SIGTTOU: return "SIGTTOU"; // 22 like TTIN for output if (tp->t_local<OSTOP) +#if !defined(_POSIX_C_SOURCE) + case SIGIO: return "SIGIO"; // 23 input/output possible signal +#endif + case SIGXCPU: return "SIGXCPU"; // 24 exceeded CPU time limit + case SIGXFSZ: return "SIGXFSZ"; // 25 exceeded file size limit + case SIGVTALRM: return "SIGVTALRM"; // 26 virtual time alarm + case SIGPROF: return "SIGPROF"; // 27 profiling time alarm +#if !defined(_POSIX_C_SOURCE) + case SIGWINCH: return "SIGWINCH"; // 28 window size changes + case SIGINFO: return "SIGINFO"; // 29 information request +#endif + case SIGUSR1: return "SIGUSR1"; // 30 user defined signal 1 + case SIGUSR2: return "SIGUSR2"; // 31 user defined signal 2 + default: + break; + } + return NULL; +} diff --git a/lldb/tools/debugserver/source/SysSignal.h b/lldb/tools/debugserver/source/SysSignal.h new file mode 100644 index 00000000000..438d137f310 --- /dev/null +++ b/lldb/tools/debugserver/source/SysSignal.h @@ -0,0 +1,23 @@ +//===-- SysSignal.h ---------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/18/07. +// +//===----------------------------------------------------------------------===// + +#ifndef __SysSignal_h__ +#define __SysSignal_h__ + +class SysSignal +{ +public: + static const char *Name(int signal); +}; + +#endif diff --git a/lldb/tools/debugserver/source/TTYState.cpp b/lldb/tools/debugserver/source/TTYState.cpp new file mode 100644 index 00000000000..28bc956dc28 --- /dev/null +++ b/lldb/tools/debugserver/source/TTYState.cpp @@ -0,0 +1,122 @@ +//===-- TTYState.cpp --------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 3/26/07. +// +//===----------------------------------------------------------------------===// + +#include "TTYState.h" +#include <fcntl.h> +#include <unistd.h> +#include <sys/signal.h> + +TTYState::TTYState() : + m_fd(-1), + m_tflags(-1), + m_ttystateErr(-1), + m_processGroup(-1) +{ +} + +TTYState::~TTYState() +{ +} + +bool +TTYState::GetTTYState (int fd, bool saveProcessGroup) +{ + if (fd >= 0 && ::isatty (fd)) + { + m_fd = fd; + m_tflags = fcntl (fd, F_GETFL, 0); + m_ttystateErr = tcgetattr (fd, &m_ttystate); + if (saveProcessGroup) + m_processGroup = tcgetpgrp (0); + else + m_processGroup = -1; + } + else + { + m_fd = -1; + m_tflags = -1; + m_ttystateErr = -1; + m_processGroup = -1; + } + return m_ttystateErr == 0; +} + +bool +TTYState::SetTTYState () const +{ + int result = 0; + if (IsValid()) + { + if (TFlagsValid()) + result = fcntl (m_fd, F_SETFL, m_tflags); + + if (TTYStateValid()) + result = tcsetattr (m_fd, TCSANOW, &m_ttystate); + + if (ProcessGroupValid()) + { + // Save the original signal handler. + void (*saved_sigttou_callback) (int) = NULL; + saved_sigttou_callback = (void (*)(int)) signal (SIGTTOU, SIG_IGN); + // Set the process group + result = tcsetpgrp (m_fd, m_processGroup); + // Restore the original signal handler. + signal (SIGTTOU, saved_sigttou_callback); + } + return true; + } + return false; +} + + + +TTYStateSwitcher::TTYStateSwitcher() : + m_currentState(~0) +{ +} + +TTYStateSwitcher::~TTYStateSwitcher() +{ +} + +bool +TTYStateSwitcher::GetState(uint32_t idx, int fd, bool saveProcessGroup) +{ + if (ValidStateIndex(idx)) + return m_ttystates[idx].GetTTYState(fd, saveProcessGroup); + return false; +} + +bool +TTYStateSwitcher::SetState(uint32_t idx) const +{ + if (!ValidStateIndex(idx)) + return false; + + // See if we already are in this state? + if (ValidStateIndex(m_currentState) && (idx == m_currentState) && m_ttystates[idx].IsValid()) + return true; + + // Set the state to match the index passed in and only update the + // current state if there are no errors. + if (m_ttystates[idx].SetTTYState()) + { + m_currentState = idx; + return true; + } + + // We failed to set the state. The tty state was invalid or not + // initialized. + return false; +} + diff --git a/lldb/tools/debugserver/source/TTYState.h b/lldb/tools/debugserver/source/TTYState.h new file mode 100644 index 00000000000..c01d5125543 --- /dev/null +++ b/lldb/tools/debugserver/source/TTYState.h @@ -0,0 +1,61 @@ +//===-- TTYState.h ----------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 3/26/07. +// +//===----------------------------------------------------------------------===// + +#ifndef __TTYState_h__ +#define __TTYState_h__ + +#include <termios.h> +#include <stdint.h> + +class TTYState +{ +public: + TTYState(); + ~TTYState(); + + bool GetTTYState (int fd, bool saveProcessGroup); + bool SetTTYState () const; + + bool IsValid() const { return FileDescriptorValid() && TFlagsValid() && TTYStateValid(); } + bool FileDescriptorValid() const { return m_fd >= 0; } + bool TFlagsValid() const { return m_tflags != -1; } + bool TTYStateValid() const { return m_ttystateErr == 0; } + bool ProcessGroupValid() const { return m_processGroup != -1; } + +protected: + int m_fd; // File descriptor + int m_tflags; + int m_ttystateErr; + struct termios m_ttystate; + pid_t m_processGroup; + +}; + + +class TTYStateSwitcher +{ +public: + TTYStateSwitcher(); + ~TTYStateSwitcher(); + + bool GetState(uint32_t idx, int fd, bool saveProcessGroup); + bool SetState(uint32_t idx) const; + uint32_t NumStates() const { return sizeof(m_ttystates)/sizeof(TTYState); } + bool ValidStateIndex(uint32_t idx) const { return idx < NumStates(); } + +protected: + mutable uint32_t m_currentState; + TTYState m_ttystates[2]; +}; + +#endif
\ No newline at end of file diff --git a/lldb/tools/debugserver/source/com.apple.debugserver.applist.plist b/lldb/tools/debugserver/source/com.apple.debugserver.applist.plist new file mode 100644 index 00000000000..4e847c20a71 --- /dev/null +++ b/lldb/tools/debugserver/source/com.apple.debugserver.applist.plist @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>Label</key> + <string>com.apple.debugserver.applist</string> + <key>UserName</key> + <string>mobile</string> + <key>ProgramArguments</key> + <array> + <string>/Developer/usr/bin/debugserver</string> + <string>--lockdown</string> + <string>--applist</string> + </array> +</dict> +</plist> diff --git a/lldb/tools/debugserver/source/com.apple.debugserver.plist b/lldb/tools/debugserver/source/com.apple.debugserver.plist new file mode 100644 index 00000000000..aa72606e098 --- /dev/null +++ b/lldb/tools/debugserver/source/com.apple.debugserver.plist @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>Label</key> + <string>com.apple.debugserver</string> + <key>UserName</key> + <string>mobile</string> + <key>ProgramArguments</key> + <array> + <string>/Developer/usr/bin/debugserver</string> + <string>--lockdown</string> + </array> +</dict> +</plist> diff --git a/lldb/tools/debugserver/source/debugserver-entitlements.plist b/lldb/tools/debugserver/source/debugserver-entitlements.plist new file mode 100644 index 00000000000..ce8bd5bc4c0 --- /dev/null +++ b/lldb/tools/debugserver/source/debugserver-entitlements.plist @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>com.apple.springboard.debugapplications</key> + <true/> + <key>run-unsigned-code</key> + <true/> + <key>seatbelt-profiles</key> + <array> + <string>debugserver</string> + </array> +</dict> +</plist> diff --git a/lldb/tools/debugserver/source/debugserver.cpp b/lldb/tools/debugserver/source/debugserver.cpp new file mode 100644 index 00000000000..f3041dbbf3a --- /dev/null +++ b/lldb/tools/debugserver/source/debugserver.cpp @@ -0,0 +1,1219 @@ +//===-- debugserver.cpp -----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include <sys/socket.h> +#include <sys/types.h> +#include <errno.h> +#include <getopt.h> +#include <netinet/in.h> +#include <sys/select.h> +#include <sys/sysctl.h> +#include <string> +#include <vector> +#include <asl.h> + +#include "CFString.h" +#include "DNB.h" +#include "DNBLog.h" +#include "DNBTimer.h" +#include "PseudoTerminal.h" +#include "RNBContext.h" +#include "RNBServices.h" +#include "RNBSocket.h" +#include "RNBRemote.h" +#include "SysSignal.h" + +// Global PID in case we get a signal and need to stop the process... +nub_process_t g_pid = INVALID_NUB_PROCESS; + +//---------------------------------------------------------------------- +// Run loop modes which determine which run loop function will be called +//---------------------------------------------------------------------- +typedef enum +{ + eRNBRunLoopModeInvalid = 0, + eRNBRunLoopModeGetStartModeFromRemoteProtocol, + eRNBRunLoopModeInferiorAttaching, + eRNBRunLoopModeInferiorLaunching, + eRNBRunLoopModeInferiorExecuting, + eRNBRunLoopModeExit +} RNBRunLoopMode; + + +//---------------------------------------------------------------------- +// Global Variables +//---------------------------------------------------------------------- +RNBRemoteSP g_remoteSP; +static int g_lockdown_opt = 0; +static int g_applist_opt = 0; +static nub_launch_flavor_t g_launch_flavor = eLaunchFlavorDefault; + +int g_isatty = 0; + +#define RNBLogSTDOUT(fmt, ...) do { if (g_isatty) { fprintf(stdout, fmt, ## __VA_ARGS__); } else { _DNBLog(0, fmt, ## __VA_ARGS__); } } while (0) +#define RNBLogSTDERR(fmt, ...) do { if (g_isatty) { fprintf(stderr, fmt, ## __VA_ARGS__); } else { _DNBLog(0, fmt, ## __VA_ARGS__); } } while (0) + +//---------------------------------------------------------------------- +// Run Loop function prototypes +//---------------------------------------------------------------------- +RNBRunLoopMode RNBRunLoopGetStartModeFromRemote (RNBRemoteSP &remote); +RNBRunLoopMode RNBRunLoopInferiorExecuting (RNBRemoteSP &remote); + + +//---------------------------------------------------------------------- +// Get our program path and arguments from the remote connection. +// We will need to start up the remote connection without a PID, get the +// arguments, wait for the new process to finish launching and hit its +// entry point, and then return the run loop mode that should come next. +//---------------------------------------------------------------------- +RNBRunLoopMode +RNBRunLoopGetStartModeFromRemote (RNBRemoteSP &remoteSP) +{ + std::string packet; + + if (remoteSP.get() != NULL) + { + RNBRemote* remote = remoteSP.get(); + RNBContext& ctx = remote->Context(); + uint32_t event_mask = RNBContext::event_read_packet_available; + + // Spin waiting to get the A packet. + while (1) + { + DNBLogThreadedIf (LOG_RNB_MAX, "%s ctx.Events().WaitForSetEvents( 0x%08x ) ...",__FUNCTION__, event_mask); + nub_event_t set_events = ctx.Events().WaitForSetEvents(event_mask); + DNBLogThreadedIf (LOG_RNB_MAX, "%s ctx.Events().WaitForSetEvents( 0x%08x ) => 0x%08x", __FUNCTION__, event_mask, set_events); + + if (set_events & RNBContext::event_read_packet_available) + { + rnb_err_t err = rnb_err; + RNBRemote::PacketEnum type; + + err = remote->HandleReceivedPacket (&type); + + // check if we tried to attach to a process + if (type == RNBRemote::vattach || type == RNBRemote::vattachwait) + { + if (err == rnb_success) + return eRNBRunLoopModeInferiorExecuting; + else + { + RNBLogSTDERR ("error: attach failed."); + return eRNBRunLoopModeExit; + } + } + + if (err == rnb_success) + { + // If we got our arguments we are ready to launch using the arguments + // and any environment variables we received. + if (type == RNBRemote::set_argv) + { + return eRNBRunLoopModeInferiorLaunching; + } + } + else if (err == rnb_not_connected) + { + RNBLogSTDERR ("error: connection lost."); + return eRNBRunLoopModeExit; + } + else + { + // a catch all for any other gdb remote packets that failed + DNBLogThreadedIf (LOG_RNB_MINIMAL, "%s Error getting packet.",__FUNCTION__); + continue; + } + + DNBLogThreadedIf (LOG_RNB_MINIMAL, "#### %s", __FUNCTION__); + } + else + { + DNBLogThreadedIf (LOG_RNB_MINIMAL, "%s Connection closed before getting \"A\" packet.", __FUNCTION__); + return eRNBRunLoopModeExit; + } + } + } + return eRNBRunLoopModeExit; +} + + +//---------------------------------------------------------------------- +// This run loop mode will wait for the process to launch and hit its +// entry point. It will currently ignore all events except for the +// process state changed event, where it watches for the process stopped +// or crash process state. +//---------------------------------------------------------------------- +RNBRunLoopMode +RNBRunLoopLaunchInferior (RNBRemoteSP &remote, const char *stdio_path) +{ + RNBContext& ctx = remote->Context(); + + // The Process stuff takes a c array, the RNBContext has a vector... + // So make up a c array. + + DNBLogThreadedIf (LOG_RNB_MINIMAL, "%s Launching '%s'...", __FUNCTION__, ctx.ArgumentAtIndex(0)); + + size_t inferior_argc = ctx.ArgumentCount(); + // Initialize inferior_argv with inferior_argc + 1 NULLs + std::vector<const char *> inferior_argv(inferior_argc + 1, NULL); + + size_t i; + for (i = 0; i < inferior_argc; i++) + inferior_argv[i] = ctx.ArgumentAtIndex(i); + + // Pass the environment array the same way: + + size_t inferior_envc = ctx.EnvironmentCount(); + // Initialize inferior_argv with inferior_argc + 1 NULLs + std::vector<const char *> inferior_envp(inferior_envc + 1, NULL); + + for (i = 0; i < inferior_envc; i++) + inferior_envp[i] = ctx.EnvironmentAtIndex(i); + + // Our launch type hasn't been set to anything concrete, so we need to + // figure our how we are going to launch automatically. + + nub_launch_flavor_t launch_flavor = g_launch_flavor; + if (launch_flavor == eLaunchFlavorDefault) + { + // Our default launch method is posix spawn + launch_flavor = eLaunchFlavorPosixSpawn; + +#if defined (__arm__) + // Check if we have an app bundle, if so launch using SpringBoard. + if (strstr(inferior_argv[0], ".app")) + { + launch_flavor = eLaunchFlavorSpringBoard; + } +#endif + } + + ctx.SetLaunchFlavor(launch_flavor); + char resolved_path[PATH_MAX]; + + // If we fail to resolve the path to our executable, then just use what we + // were given and hope for the best + if ( !DNBResolveExecutablePath (inferior_argv[0], resolved_path, sizeof(resolved_path)) ) + ::strncpy(resolved_path, inferior_argv[0], sizeof(resolved_path)); + + char launch_err_str[PATH_MAX]; + launch_err_str[0] = '\0'; + nub_process_t pid = DNBProcessLaunch (resolved_path, + &inferior_argv[0], + &inferior_envp[0], + stdio_path, + launch_flavor, + launch_err_str, + sizeof(launch_err_str)); + + g_pid = pid; + + if (pid == INVALID_NUB_PROCESS && strlen(launch_err_str) > 0) + { + DNBLogThreaded ("%s DNBProcessLaunch() returned error: '%s'", __FUNCTION__); + ctx.LaunchStatus().SetError(-1, DNBError::Generic); + ctx.LaunchStatus().SetErrorString(launch_err_str); + } + else + ctx.LaunchStatus().Clear(); + + if (remote->Comm().IsConnected()) + { + // It we are connected already, the next thing gdb will do is ask + // whether the launch succeeded, and if not, whether there is an + // error code. So we need to fetch one packet from gdb before we wait + // on the stop from the target. + + uint32_t event_mask = RNBContext::event_read_packet_available; + nub_event_t set_events = ctx.Events().WaitForSetEvents(event_mask); + + if (set_events & RNBContext::event_read_packet_available) + { + rnb_err_t err = rnb_err; + RNBRemote::PacketEnum type; + + err = remote->HandleReceivedPacket (&type); + + if (err != rnb_success) + { + DNBLogThreadedIf (LOG_RNB_MINIMAL, "%s Error getting packet.", __FUNCTION__); + return eRNBRunLoopModeExit; + } + if (type != RNBRemote::query_launch_success) + { + DNBLogThreadedIf (LOG_RNB_MINIMAL, "%s Didn't get the expected qLaunchSuccess packet.", __FUNCTION__); + } + } + } + + while (pid != INVALID_NUB_PROCESS) + { + // Wait for process to start up and hit entry point + DNBLogThreadedIf (LOG_RNB_EVENTS, "%s DNBProcessWaitForEvent (%4.4x, eEventProcessRunningStateChanged | eEventProcessStoppedStateChanged, true, INFINITE)...", __FUNCTION__, pid); + nub_event_t set_events = DNBProcessWaitForEvents (pid, eEventProcessRunningStateChanged | eEventProcessStoppedStateChanged, true, NULL); + DNBLogThreadedIf (LOG_RNB_EVENTS, "%s DNBProcessWaitForEvent (%4.4x, eEventProcessRunningStateChanged | eEventProcessStoppedStateChanged, true, INFINITE) => 0x%8.8x", __FUNCTION__, pid, set_events); + + if (set_events == 0) + { + pid = INVALID_NUB_PROCESS; + g_pid = pid; + } + else + { + if (set_events & (eEventProcessRunningStateChanged | eEventProcessStoppedStateChanged)) + { + nub_state_t pid_state = DNBProcessGetState (pid); + DNBLogThreadedIf (LOG_RNB_EVENTS, "%s process %4.4x state changed (eEventProcessStateChanged): %s", __FUNCTION__, pid, DNBStateAsString(pid_state)); + + switch (pid_state) + { + default: + case eStateInvalid: + case eStateUnloaded: + case eStateAttaching: + case eStateLaunching: + case eStateSuspended: + break; // Ignore + + case eStateRunning: + case eStateStepping: + // Still waiting to stop at entry point... + break; + + case eStateStopped: + case eStateCrashed: + ctx.SetProcessID(pid); + return eRNBRunLoopModeInferiorExecuting; + + case eStateDetached: + case eStateExited: + pid = INVALID_NUB_PROCESS; + g_pid = pid; + return eRNBRunLoopModeExit; + } + } + + DNBProcessResetEvents(pid, set_events); + } + } + + return eRNBRunLoopModeExit; +} + + +//---------------------------------------------------------------------- +// This run loop mode will wait for the process to launch and hit its +// entry point. It will currently ignore all events except for the +// process state changed event, where it watches for the process stopped +// or crash process state. +//---------------------------------------------------------------------- +RNBRunLoopMode +RNBRunLoopLaunchAttaching (RNBRemoteSP &remote, nub_process_t attach_pid, nub_process_t& pid) +{ + RNBContext& ctx = remote->Context(); + + DNBLogThreadedIf (LOG_RNB_MINIMAL, "%s Attaching to pid %i...", __FUNCTION__, attach_pid); + char err_str[1024]; + pid = DNBProcessAttach (attach_pid, NULL, err_str, sizeof(err_str)); + g_pid = pid; + + if (pid == INVALID_NUB_PROCESS) + { + ctx.LaunchStatus().SetError(-1, DNBError::Generic); + if (err_str[0]) + ctx.LaunchStatus().SetErrorString(err_str); + return eRNBRunLoopModeExit; + } + else + { + + ctx.SetProcessID(pid); + return eRNBRunLoopModeInferiorExecuting; + } +} + +//---------------------------------------------------------------------- +// Watch for signals: +// SIGINT: so we can halt our inferior. (disabled for now) +// SIGPIPE: in case our child process dies +//---------------------------------------------------------------------- +int g_sigint_received = 0; +int g_sigpipe_received = 0; +void +signal_handler(int signo) +{ + DNBLogThreadedIf (LOG_RNB_MINIMAL, "%s (%s)", __FUNCTION__, SysSignal::Name(signo)); + + switch (signo) + { + case SIGINT: + g_sigint_received++; + if (g_pid != INVALID_NUB_PROCESS) + { + // Only send a SIGINT once... + if (g_sigint_received == 1) + { + switch (DNBProcessGetState (g_pid)) + { + case eStateRunning: + case eStateStepping: + DNBProcessSignal (g_pid, SIGSTOP); + return; + } + } + } + exit (SIGINT); + break; + + case SIGPIPE: + g_sigpipe_received = 1; + break; + } +} + +// Return the new run loop mode based off of the current process state +RNBRunLoopMode +HandleProcessStateChange (RNBRemoteSP &remote, bool initialize) +{ + RNBContext& ctx = remote->Context(); + nub_process_t pid = ctx.ProcessID(); + + if (pid == INVALID_NUB_PROCESS) + { + DNBLogThreadedIf (LOG_RNB_MINIMAL, "#### %s error: pid invalid, exiting...", __FUNCTION__); + return eRNBRunLoopModeExit; + } + nub_state_t pid_state = DNBProcessGetState (pid); + + DNBLogThreadedIf (LOG_RNB_MINIMAL, "%s (&remote, initialize=%i) pid_state = %s", __FUNCTION__, (int)initialize, DNBStateAsString (pid_state)); + + switch (pid_state) + { + case eStateInvalid: + case eStateUnloaded: + // Something bad happened + return eRNBRunLoopModeExit; + break; + + case eStateAttaching: + case eStateLaunching: + return eRNBRunLoopModeInferiorExecuting; + + case eStateSuspended: + case eStateCrashed: + case eStateStopped: + // If we stop due to a signal, so clear the fact that we got a SIGINT + // so we can stop ourselves again (but only while our inferior + // process is running..) + g_sigint_received = 0; + if (initialize == false) + { + // Compare the last stop count to our current notion of a stop count + // to make sure we don't notify more than once for a given stop. + nub_size_t prev_pid_stop_count = ctx.GetProcessStopCount(); + bool pid_stop_count_changed = ctx.SetProcessStopCount(DNBProcessGetStopCount(pid)); + if (pid_stop_count_changed) + { + remote->FlushSTDIO(); + + if (ctx.GetProcessStopCount() == 1) + { + DNBLogThreadedIf (LOG_RNB_MINIMAL, "%s (&remote, initialize=%i) pid_state = %s pid_stop_count %u (old %u)) Notify??? no, first stop...", __FUNCTION__, (int)initialize, DNBStateAsString (pid_state), ctx.GetProcessStopCount(), prev_pid_stop_count); + } + else + { + + DNBLogThreadedIf (LOG_RNB_MINIMAL, "%s (&remote, initialize=%i) pid_state = %s pid_stop_count %u (old %u)) Notify??? YES!!!", __FUNCTION__, (int)initialize, DNBStateAsString (pid_state), ctx.GetProcessStopCount(), prev_pid_stop_count); + remote->NotifyThatProcessStopped (); + } + } + else + { + DNBLogThreadedIf (LOG_RNB_MINIMAL, "%s (&remote, initialize=%i) pid_state = %s pid_stop_count %u (old %u)) Notify??? skipping...", __FUNCTION__, (int)initialize, DNBStateAsString (pid_state), ctx.GetProcessStopCount(), prev_pid_stop_count); + } + } + return eRNBRunLoopModeInferiorExecuting; + + case eStateStepping: + case eStateRunning: + return eRNBRunLoopModeInferiorExecuting; + + case eStateExited: + remote->HandlePacket_last_signal(NULL); + return eRNBRunLoopModeExit; + + } + + // Catch all... + return eRNBRunLoopModeExit; +} +// This function handles the case where our inferior program is stopped and +// we are waiting for gdb remote protocol packets. When a packet occurs that +// makes the inferior run, we need to leave this function with a new state +// as the return code. +RNBRunLoopMode +RNBRunLoopInferiorExecuting (RNBRemoteSP &remote) +{ + DNBLogThreadedIf (LOG_RNB_MINIMAL, "#### %s", __FUNCTION__); + RNBContext& ctx = remote->Context(); + + // Init our mode and set 'is_running' based on the current process state + RNBRunLoopMode mode = HandleProcessStateChange (remote, true); + + while (ctx.ProcessID() != INVALID_NUB_PROCESS) + { + + std::string set_events_str; + uint32_t event_mask = ctx.NormalEventBits(); + + if (!ctx.ProcessStateRunning()) + { + // Clear the stdio bits if we are not running so we don't send any async packets + event_mask &= ~RNBContext::event_proc_stdio_available; + } + + // We want to make sure we consume all process state changes and have + // whomever is notifying us to wait for us to reset the event bit before + // continuing. + //ctx.Events().SetResetAckMask (RNBContext::event_proc_state_changed); + + DNBLogThreadedIf (LOG_RNB_EVENTS, "%s ctx.Events().WaitForSetEvents(0x%08x) ...",__FUNCTION__, event_mask); + nub_event_t set_events = ctx.Events().WaitForSetEvents(event_mask); + DNBLogThreadedIf (LOG_RNB_EVENTS, "%s ctx.Events().WaitForSetEvents(0x%08x) => 0x%08x (%s)",__FUNCTION__, event_mask, set_events, ctx.EventsAsString(set_events, set_events_str)); + + if (set_events) + { + if ((set_events & RNBContext::event_proc_thread_exiting) || + (set_events & RNBContext::event_proc_stdio_available)) + { + remote->FlushSTDIO(); + } + + if (set_events & RNBContext::event_read_packet_available) + { + // handleReceivedPacket will take care of resetting the + // event_read_packet_available events when there are no more... + set_events ^= RNBContext::event_read_packet_available; + + if (ctx.ProcessStateRunning()) + { + if (remote->HandleAsyncPacket() == rnb_not_connected) + { + // TODO: connect again? Exit? + } + } + else + { + if (remote->HandleReceivedPacket() == rnb_not_connected) + { + // TODO: connect again? Exit? + } + } + } + + if (set_events & RNBContext::event_proc_state_changed) + { + mode = HandleProcessStateChange (remote, false); + ctx.Events().ResetEvents(RNBContext::event_proc_state_changed); + set_events ^= RNBContext::event_proc_state_changed; + } + + if (set_events & RNBContext::event_proc_thread_exiting) + { + mode = eRNBRunLoopModeExit; + } + + if (set_events & RNBContext::event_read_thread_exiting) + { + // Out remote packet receiving thread exited, exit for now. + if (ctx.HasValidProcessID()) + { + // TODO: We should add code that will leave the current process + // in its current state and listen for another connection... + if (ctx.ProcessStateRunning()) + { + DNBProcessKill (ctx.ProcessID()); + } + } + mode = eRNBRunLoopModeExit; + } + } + + // Reset all event bits that weren't reset for now... + if (set_events != 0) + ctx.Events().ResetEvents(set_events); + + if (mode != eRNBRunLoopModeInferiorExecuting) + break; + } + + return mode; +} + + +//---------------------------------------------------------------------- +// Convenience function to set up the remote listening port +// Returns 1 for success 0 for failure. +//---------------------------------------------------------------------- + +static int +StartListening (RNBRemoteSP remoteSP, int listen_port) +{ + if (!remoteSP->Comm().IsConnected()) + { + RNBLogSTDOUT ("Listening to port %i...\n", listen_port); + if (remoteSP->Comm().Listen(listen_port) != rnb_success) + { + RNBLogSTDERR ("Failed to get connection from a remote gdb process.\n"); + return 0; + } + else + { + remoteSP->StartReadRemoteDataThread(); + } + } + return 1; +} + +//---------------------------------------------------------------------- +// ASL Logging callback that can be registered with DNBLogSetLogCallback +//---------------------------------------------------------------------- +void +ASLLogCallback(void *baton, uint32_t flags, const char *format, va_list args) +{ + if (format == NULL) + return; + static aslmsg g_aslmsg = NULL; + if (g_aslmsg == NULL) + { + g_aslmsg = ::asl_new (ASL_TYPE_MSG); + char asl_key_sender[PATH_MAX]; + snprintf(asl_key_sender, sizeof(asl_key_sender), "com.apple.%s-%g", DEBUGSERVER_PROGRAM_NAME, DEBUGSERVER_VERSION_NUM); + ::asl_set (g_aslmsg, ASL_KEY_SENDER, asl_key_sender); + } + + int asl_level; + if (flags & DNBLOG_FLAG_FATAL) asl_level = ASL_LEVEL_CRIT; + else if (flags & DNBLOG_FLAG_ERROR) asl_level = ASL_LEVEL_ERR; + else if (flags & DNBLOG_FLAG_WARNING) asl_level = ASL_LEVEL_WARNING; + else if (flags & DNBLOG_FLAG_VERBOSE) asl_level = ASL_LEVEL_WARNING; //ASL_LEVEL_INFO; + else asl_level = ASL_LEVEL_WARNING; //ASL_LEVEL_DEBUG; + + ::asl_vlog (NULL, g_aslmsg, asl_level, format, args); +} + +//---------------------------------------------------------------------- +// FILE based Logging callback that can be registered with +// DNBLogSetLogCallback +//---------------------------------------------------------------------- +void +FileLogCallback(void *baton, uint32_t flags, const char *format, va_list args) +{ + if (baton == NULL || format == NULL) + return; + + ::vfprintf ((FILE *)baton, format, args); + ::fprintf ((FILE *)baton, "\n"); +} + + +void +show_usage_and_exit (int exit_code) +{ + RNBLogSTDERR ("Usage:\n %s host:port [program-name program-arg1 program-arg2 ...]\n", DEBUGSERVER_PROGRAM_NAME); + RNBLogSTDERR (" %s /path/file [program-name program-arg1 program-arg2 ...]\n", DEBUGSERVER_PROGRAM_NAME); + RNBLogSTDERR (" %s host:port --attach=<pid>\n", DEBUGSERVER_PROGRAM_NAME); + RNBLogSTDERR (" %s /path/file --attach=<pid>\n", DEBUGSERVER_PROGRAM_NAME); + RNBLogSTDERR (" %s host:port --attach=<process_name>\n", DEBUGSERVER_PROGRAM_NAME); + RNBLogSTDERR (" %s /path/file --attach=<process_name>\n", DEBUGSERVER_PROGRAM_NAME); + exit (exit_code); +} + + +//---------------------------------------------------------------------- +// option descriptors for getopt_long() +//---------------------------------------------------------------------- +static struct option g_long_options[] = +{ + { "attach", required_argument, NULL, 'a' }, + { "debug", no_argument, NULL, 'g' }, + { "verbose", no_argument, NULL, 'v' }, + { "lockdown", no_argument, &g_lockdown_opt, 1 }, // short option "-k" + { "applist", no_argument, &g_applist_opt, 1 }, // short option "-t" + { "log-file", required_argument, NULL, 'l' }, + { "log-flags", required_argument, NULL, 'f' }, + { "launch", required_argument, NULL, 'x' }, // Valid values are "auto", "posix-spawn", "fork-exec", "springboard" (arm only) + { "waitfor", required_argument, NULL, 'w' }, // Wait for a process whose name starts with ARG + { "waitfor-interval", required_argument, NULL, 'i' }, // Time in usecs to wait between sampling the pid list when waiting for a process by name + { "waitfor-duration", required_argument, NULL, 'd' }, // The time in seconds to wait for a process to show up by name + { "native-regs", no_argument, NULL, 'r' }, // Specify to use the native registers instead of the gdb defaults for the architecture. + { "stdio-path", required_argument, NULL, 's' }, // Set the STDIO path to be used when launching applications + { "setsid", no_argument, NULL, 'S' }, // call setsid() to make debugserver run in its own sessions + { NULL, 0, NULL, 0 } +}; + + +//---------------------------------------------------------------------- +// main +//---------------------------------------------------------------------- +int +main (int argc, char *argv[]) +{ + g_isatty = ::isatty (STDIN_FILENO); + + // ::printf ("uid=%u euid=%u gid=%u egid=%u\n", + // getuid(), + // geteuid(), + // getgid(), + // getegid()); + + + // signal (SIGINT, signal_handler); + signal (SIGPIPE, signal_handler); + + int i; + int attach_pid = INVALID_NUB_PROCESS; + + FILE* log_file = NULL; + uint32_t log_flags = 0; + // Parse our options + int ch; + int long_option_index = 0; + int use_native_registers = 0; + int debug = 0; + std::string compile_options; + std::string waitfor_pid_name; // Wait for a process that starts with this name + std::string attach_pid_name; + std::string stdio_path; + useconds_t waitfor_interval = 1000; // Time in usecs between process lists polls when waiting for a process by name, default 1 msec. + useconds_t waitfor_duration = 0; // Time in seconds to wait for a process by name, 0 means wait forever. + +#if !defined (DNBLOG_ENABLED) + compile_options += "(no-logging) "; +#endif + + RNBRunLoopMode start_mode = eRNBRunLoopModeExit; + + while ((ch = getopt_long(argc, argv, "a:d:gi:vktl:f:w:x:r", g_long_options, &long_option_index)) != -1) + { + DNBLogDebug("option: ch == %c (0x%2.2x) --%s%c%s\n", + ch, (uint8_t)ch, + g_long_options[long_option_index].name, + g_long_options[long_option_index].has_arg ? '=' : ' ', + optarg ? optarg : ""); + switch (ch) + { + case 0: // Any optional that auto set themselves will return 0 + break; + + case 'a': + if (optarg && optarg[0]) + { + if (isdigit(optarg[0])) + { + char *end = NULL; + attach_pid = strtoul(optarg, &end, 0); + if (end == NULL || *end != '\0') + { + RNBLogSTDERR ("error: invalid pid option '%s'\n", optarg); + exit (4); + } + } + else + { + attach_pid_name = optarg; + } + start_mode = eRNBRunLoopModeInferiorAttaching; + } + break; + + // --waitfor=NAME + case 'w': + if (optarg && optarg[0]) + { + waitfor_pid_name = optarg; + start_mode = eRNBRunLoopModeInferiorAttaching; + } + break; + + // --waitfor-interval=USEC + case 'i': + if (optarg && optarg[0]) + { + char *end = NULL; + waitfor_interval = strtoul(optarg, &end, 0); + if (end == NULL || *end != '\0') + { + RNBLogSTDERR ("error: invalid waitfor-interval option value '%s'.\n", optarg); + exit (6); + } + } + break; + + // --waitfor-duration=SEC + case 'd': + if (optarg && optarg[0]) + { + char *end = NULL; + waitfor_duration = strtoul(optarg, &end, 0); + if (end == NULL || *end != '\0') + { + RNBLogSTDERR ("error: invalid waitfor-duration option value '%s'.\n", optarg); + exit (7); + } + } + break; + + case 'x': + if (optarg && optarg[0]) + { + if (strcasecmp(optarg, "auto") == 0) + g_launch_flavor = eLaunchFlavorDefault; + else if (strcasestr(optarg, "posix") == optarg) + g_launch_flavor = eLaunchFlavorPosixSpawn; + else if (strcasestr(optarg, "fork") == optarg) + g_launch_flavor = eLaunchFlavorForkExec; +#if defined (__arm__) + else if (strcasestr(optarg, "spring") == optarg) + g_launch_flavor = eLaunchFlavorSpringBoard; +#endif + else + { + RNBLogSTDERR ("error: invalid TYPE for the --launch=TYPE (-x TYPE) option: '%s'\n", optarg); + RNBLogSTDERR ("Valid values TYPE are:\n"); + RNBLogSTDERR (" auto Auto-detect the best launch method to use.\n"); + RNBLogSTDERR (" posix Launch the executable using posix_spawn.\n"); + RNBLogSTDERR (" fork Launch the executable using fork and exec.\n"); +#if defined (__arm__) + RNBLogSTDERR (" spring Launch the executable through Springboard.\n"); +#endif + exit (5); + } + } + break; + + case 'l': // Set Log File + if (optarg && optarg[0]) + { + if (strcasecmp(optarg, "stdout") == 0) + log_file = stdout; + else if (strcasecmp(optarg, "stderr") == 0) + log_file = stderr; + else + log_file = fopen(optarg, "w+"); + + if (log_file == NULL) + { + const char *errno_str = strerror(errno); + RNBLogSTDERR ("Failed to open log file '%s' for writing: errno = %i (%s)", optarg, errno, errno_str ? errno_str : "unknown error"); + } + } + break; + + case 'f': // Log Flags + if (optarg && optarg[0]) + log_flags = strtoul(optarg, NULL, 0); + break; + + case 'g': + debug = 1; + DNBLogSetDebug(1); + break; + + case 't': + g_applist_opt = 1; + break; + + case 'k': + g_lockdown_opt = 1; + break; + + case 'r': + use_native_registers = 1; + break; + + case 'v': + DNBLogSetVerbose(1); + break; + + case 's': + stdio_path = optarg; + break; + + case 'S': + // Put debugserver into a new session. Terminals group processes + // into sessions and when a special terminal key sequences + // (like control+c) are typed they can cause signals to go out to + // all processes in a session. Using this --setsid (-S) option + // will cause debugserver to run in its own sessions and be free + // from such issues. + // + // This is useful when debugserver is spawned from a command + // line application that uses debugserver to do the debugging, + // yet that application doesn't want debugserver receiving the + // signals sent to the session (i.e. dying when anyone hits ^C). + setsid(); + break; + } + } + + // Skip any options we consumed with getopt_long + argc -= optind; + argv += optind; + + g_remoteSP.reset (new RNBRemote (use_native_registers)); + + RNBRemote *remote = g_remoteSP.get(); + if (remote == NULL) + { + RNBLogSTDERR ("error: failed to create a remote connection class\n"); + return -1; + } + + RNBContext& ctx = remote->Context(); + + + // It is ok for us to set NULL as the logfile (this will disable any logging) + + if (log_file != NULL) + { + DNBLogSetLogCallback(FileLogCallback, log_file); + // If our log file was set, yet we have no log flags, log everything! + if (log_flags == 0) + log_flags = LOG_ALL | LOG_RNB_ALL; + + DNBLogSetLogMask (log_flags); + } + else + { + // Enable DNB logging + DNBLogSetLogCallback(ASLLogCallback, NULL); + DNBLogSetLogMask (log_flags); + + } + + if (DNBLogEnabled()) + { + for (i=0; i<argc; i++) + DNBLogDebug("argv[%i] = %s", i, argv[i]); + } + + // Now that we have read in the options and enabled logging, initialize + // the rest of RNBRemote + RNBRemote::InitializeRegisters (use_native_registers); + + + // as long as we're dropping remotenub in as a replacement for gdbserver, + // explicitly note that this is not gdbserver. + + RNBLogSTDOUT ("%s-%g %sfor %s.\n", + DEBUGSERVER_PROGRAM_NAME, + DEBUGSERVER_VERSION_NUM, + compile_options.c_str(), + RNB_ARCH); + + int listen_port = INT32_MAX; + char str[PATH_MAX]; + + if (g_lockdown_opt == 0 && g_applist_opt == 0) + { + // Make sure we at least have port + if (argc < 1) + { + show_usage_and_exit (1); + } + // accept 'localhost:' prefix on port number + + int items_scanned = ::sscanf (argv[0], "%[^:]:%i", str, &listen_port); + if (items_scanned == 2) + { + DNBLogDebug("host = '%s' port = %i", str, listen_port); + } + else if (argv[0][0] == '/') + { + listen_port = INT32_MAX; + strncpy(str, argv[0], sizeof(str)); + } + else + { + show_usage_and_exit (2); + } + + // We just used the 'host:port' or the '/path/file' arg... + argc--; + argv++; + + } + + // If we know we're waiting to attach, we don't need any of this other info. + if (start_mode != eRNBRunLoopModeInferiorAttaching) + { + if (argc == 0 || g_lockdown_opt) + { + if (g_lockdown_opt != 0) + { + // Work around for SIGPIPE crashes due to posix_spawn issue. + // We have to close STDOUT and STDERR, else the first time we + // try and do any, we get SIGPIPE and die as posix_spawn is + // doing bad things with our file descriptors at the moment. + int null = open("/dev/null", O_RDWR); + dup2(null, STDOUT_FILENO); + dup2(null, STDERR_FILENO); + } + else if (g_applist_opt != 0) + { + // List all applications we are able to see + std::string applist_plist; + int err = ListApplications(applist_plist, false, false); + if (err == 0) + { + fputs (applist_plist.c_str(), stdout); + } + else + { + RNBLogSTDERR ("error: ListApplications returned error %i\n", err); + } + // Exit with appropriate error if we were asked to list the applications + // with no other args were given (and we weren't trying to do this over + // lockdown) + return err; + } + + DNBLogDebug("Get args from remote protocol..."); + start_mode = eRNBRunLoopModeGetStartModeFromRemoteProtocol; + } + else + { + start_mode = eRNBRunLoopModeInferiorLaunching; + // Fill in the argv array in the context from the rest of our args. + // Skip the name of this executable and the port number + for (int i = 0; i < argc; i++) + { + DNBLogDebug("inferior_argv[%i] = '%s'", i, argv[i]); + ctx.PushArgument (argv[i]); + } + } + } + + if (start_mode == eRNBRunLoopModeExit) + return -1; + + RNBRunLoopMode mode = start_mode; + char err_str[1024] = {'\0'}; + + while (mode != eRNBRunLoopModeExit) + { + switch (mode) + { + case eRNBRunLoopModeGetStartModeFromRemoteProtocol: +#if defined (__arm__) + if (g_lockdown_opt) + { + if (!g_remoteSP->Comm().IsConnected()) + { + if (g_remoteSP->Comm().ConnectToService () != rnb_success) + { + RNBLogSTDERR ("Failed to get connection from a remote gdb process.\n"); + mode = eRNBRunLoopModeExit; + } + else if (g_applist_opt != 0) + { + // List all applications we are able to see + std::string applist_plist; + if (ListApplications(applist_plist, false, false) == 0) + { + DNBLogDebug("Task list: %s", applist_plist.c_str()); + + g_remoteSP->Comm().Write(applist_plist.c_str(), applist_plist.size()); + // Issue a read that will never yield any data until the other side + // closes the socket so this process doesn't just exit and cause the + // socket to close prematurely on the other end and cause data loss. + std::string buf; + g_remoteSP->Comm().Read(buf); + } + g_remoteSP->Comm().Disconnect(false); + mode = eRNBRunLoopModeExit; + break; + } + else + { + // Start watching for remote packets + g_remoteSP->StartReadRemoteDataThread(); + } + } + } + else +#endif + if (listen_port != INT32_MAX) + { + if (!StartListening (g_remoteSP, listen_port)) + mode = eRNBRunLoopModeExit; + } + else if (str[0] == '/') + { + if (g_remoteSP->Comm().OpenFile (str)) + mode = eRNBRunLoopModeExit; + } + if (mode != eRNBRunLoopModeExit) + { + RNBLogSTDOUT ("Got a connection, waiting for process information for launching or attaching.\n"); + + mode = RNBRunLoopGetStartModeFromRemote (g_remoteSP); + } + break; + + case eRNBRunLoopModeInferiorAttaching: + if (!waitfor_pid_name.empty()) + { + // Set our end wait time if we are using a waitfor-duration + // option that may have been specified + struct timespec attach_timeout_abstime, *timeout_ptr = NULL; + if (waitfor_duration != 0) + { + DNBTimer::OffsetTimeOfDay(&attach_timeout_abstime, waitfor_duration, 0); + timeout_ptr = &attach_timeout_abstime; + } + nub_launch_flavor_t launch_flavor = g_launch_flavor; + if (launch_flavor == eLaunchFlavorDefault) + { + // Our default launch method is posix spawn + launch_flavor = eLaunchFlavorPosixSpawn; + +#if defined (__arm__) + // Check if we have an app bundle, if so launch using SpringBoard. + if (waitfor_pid_name.find (".app") != std::string::npos) + { + launch_flavor = eLaunchFlavorSpringBoard; + } +#endif + } + + ctx.SetLaunchFlavor(launch_flavor); + + nub_process_t pid = DNBProcessAttachWait (waitfor_pid_name.c_str(), launch_flavor, timeout_ptr, waitfor_interval, err_str, sizeof(err_str)); + g_pid = pid; + + if (pid == INVALID_NUB_PROCESS) + { + ctx.LaunchStatus().SetError(-1, DNBError::Generic); + if (err_str[0]) + ctx.LaunchStatus().SetErrorString(err_str); + RNBLogSTDERR ("error: failed to attach to process named: \"%s\" %s", waitfor_pid_name.c_str(), err_str); + mode = eRNBRunLoopModeExit; + } + else + { + ctx.SetProcessID(pid); + mode = eRNBRunLoopModeInferiorExecuting; + } + } + else if (attach_pid != INVALID_NUB_PROCESS) + { + + RNBLogSTDOUT ("Attaching to process %i...\n", attach_pid); + nub_process_t attached_pid; + mode = RNBRunLoopLaunchAttaching (g_remoteSP, attach_pid, attached_pid); + if (mode != eRNBRunLoopModeInferiorExecuting) + { + const char *error_str = remote->Context().LaunchStatus().AsString(); + RNBLogSTDERR ("error: failed to attach process %i: %s\n", attach_pid, error_str ? error_str : "unknown error."); + mode = eRNBRunLoopModeExit; + } + } + else if (!attach_pid_name.empty ()) + { + struct timespec attach_timeout_abstime, *timeout_ptr = NULL; + if (waitfor_duration != 0) + { + DNBTimer::OffsetTimeOfDay(&attach_timeout_abstime, waitfor_duration, 0); + timeout_ptr = &attach_timeout_abstime; + } + + nub_process_t pid = DNBProcessAttachByName (attach_pid_name.c_str(), timeout_ptr, err_str, sizeof(err_str)); + g_pid = pid; + if (pid == INVALID_NUB_PROCESS) + { + ctx.LaunchStatus().SetError(-1, DNBError::Generic); + if (err_str[0]) + ctx.LaunchStatus().SetErrorString(err_str); + RNBLogSTDERR ("error: failed to attach to process named: \"%s\" %s", waitfor_pid_name.c_str(), err_str); + mode = eRNBRunLoopModeExit; + } + else + { + ctx.SetProcessID(pid); + mode = eRNBRunLoopModeInferiorExecuting; + } + + } + else + { + RNBLogSTDERR ("error: asked to attach with empty name and invalid PID."); + mode = eRNBRunLoopModeExit; + } + + if (mode != eRNBRunLoopModeExit) + { + if (listen_port != INT32_MAX) + { + if (!StartListening (g_remoteSP, listen_port)) + mode = eRNBRunLoopModeExit; + } + else if (str[0] == '/') + { + if (g_remoteSP->Comm().OpenFile (str)) + mode = eRNBRunLoopModeExit; + } + if (mode != eRNBRunLoopModeExit) + RNBLogSTDOUT ("Got a connection, waiting for debugger instructions for process %d.\n", attach_pid); + } + break; + + case eRNBRunLoopModeInferiorLaunching: + mode = RNBRunLoopLaunchInferior (g_remoteSP, stdio_path.empty() ? NULL : stdio_path.c_str()); + + if (mode == eRNBRunLoopModeInferiorExecuting) + { + if (listen_port != INT32_MAX) + { + if (!StartListening (g_remoteSP, listen_port)) + mode = eRNBRunLoopModeExit; + } + else if (str[0] == '/') + { + if (g_remoteSP->Comm().OpenFile (str)) + mode = eRNBRunLoopModeExit; + } + + if (mode != eRNBRunLoopModeExit) + RNBLogSTDOUT ("Got a connection, waiting for debugger instructions.\n"); + } + else + { + const char *error_str = remote->Context().LaunchStatus().AsString(); + RNBLogSTDERR ("error: failed to launch process %s: %s\n", argv[0], error_str ? error_str : "unknown error."); + } + break; + + case eRNBRunLoopModeInferiorExecuting: + mode = RNBRunLoopInferiorExecuting(g_remoteSP); + break; + + default: + mode = eRNBRunLoopModeExit; + case eRNBRunLoopModeExit: + break; + } + } + + g_remoteSP->StopReadRemoteDataThread (); + g_remoteSP->Context().SetProcessID(INVALID_NUB_PROCESS); + + return 0; +} |