Is there an addr2line which analyzes Code in Memory? - gdb

I'm using addr2line to analyze the stacktrace after an exception was thrown (using backtrace and backtrace_symbols). Currently this takes about 2 seconds to analyze the 120Mb binary (debug build) from disk.
Is there a library (LGPL), which does the same job by analyzing the code segment in memory? Of course this limits me to analyze only the own application - but in my use-case this is enough.

You want (BSD licensed) libunwind:
void backtrace(void)
{
int r;
unw_cursor_t cursor; unw_context_t uc;
unw_word_t ip, sp;
char symname[100];
unw_getcontext(&uc);
unw_init_local(&cursor, &uc);
while (unw_step(&cursor) > 0) {
r = unw_get_reg(&cursor, UNW_REG_IP, &ip);
assert(r == 0);
r = unw_get_reg(&cursor, UNW_REG_SP, &sp);
assert(r == 0);
r = unw_get_proc_name(&cursor, symname, sizeof(symname), NULL);
assert(r == 0);
fprintf(stderr, "%s: ip: %lx, sp: %lx\n", symname, (long) ip, (long) sp);
}
}
Note that unw_get_proc_name() will fail (return non 0) if you strip the symbol and string tables from your binaries.

maybe you can use the executable recognized by the proc filesystem?
(or is it actually an executable within the memory of a process?)
addr2line -e /proc/32213/exe

Libbacktrace, which is part of the GCC distribution does this. It handles stack unwinding, ELF symbols and debugging symbols, i.e. it provides the functionality of both libunwind and addr2line.
I have put a standalone fork here:
https://github.com/ErwanLegrand/libbacktrace

Related

LoadEnclaveImage "A device attached to the system is not functioning"

I'm trying to load a signed DLL into a VBS enclave and LoadEnclaveImage is returning A device attached to the system is not functioning.
Hyper-V, Secure Boot, & TPM 2.0 are all functioning, so I'm not quite sure as to what the error is referring to.
Sample Code:
if (IsEnclaveTypeSupported(ENCLAVE_TYPE_VBS))
{
DWORD lpError = 0;
ENCLAVE_CREATE_INFO_VBS vci = { 0 };
vci.Flags = 1;
PVOID enclave = CreateEnclave(GetCurrentProcess(),
NULL,
1024 * 1024 * 2,
NULL,
ENCLAVE_TYPE_VBS,
&vci,
sizeof(ENCLAVE_CREATE_INFO_VBS),
&lpError);
if (enclave != NULL)
{
auto lib = LoadLibrary(L"kernelbase.dll");
auto addr = (__LoadEnclaveImage)GetProcAddress(lib, "LoadEnclaveImageW");
if (addr(enclave, L"...\testme.dll"))
{
printf("Worked!\n");
}
else {
printf("Failed to load image\n");
printf(GetLastErrorAsString().c_str());
}
}
else
{
printf(GetLastErrorAsString().c_str());
}
}
else {
printf("VBS not supported\n");
}
I got the same general error when loading a signed DLL, so I looked for usages of LoadEnclaveImageW in other system binaries using Static Import Finder, and found it in SgrmBroker.exe where it loads "SgrmEnclave_secure.dll". Attempting to use LoadEnclaveImageW with that DLL was successful.
Digging deeper into the PE structure of the "SgrmEnclave_secure.dll" file, we can see that a value is defined for EnclaveConfigurationPointer in the IMAGE_LOAD_CONFIG_DIRECTORY64 structure (see screenshot from PE-bear).
This pointer points to an IMAGE_ENCLAVE_CONFIG64 structure and this screenshot shows what it looks like when parsed in Ghidra. The ImportList member is an RVA for a series of IMAGE_ENCLAVE_IMPORT structures.
So it looks like these structures need to be defined in the PE. This can be done using the /ENCLAVE option in the linker. Not sure if there are additional requirements. Should you get further with this, I'd be interested to know.

How to unwind the stack to get backtrace for the specified stack pointer (SP)?

I'm writing this for Android (ARM only), but I believe the principle is the same for generic Linux as well.
I'm trying to capture the stack trace from within the signal handler, so that I can log it when my app crashes. This is what I've come up with using <unwind.h>.
Initialization:
struct sigaction signalhandlerDescriptor;
memset(&signalhandlerDescriptor, 0, sizeof(signalhandlerDescriptor));
signalhandlerDescriptor.sa_flags = SA_SIGINFO;
signalhandlerDescriptor._u._sa_sigaction = signalHandler;
sigaction(SIGSEGV, &signalhandlerDescriptor, 0);
The code itself:
struct BacktraceState
{
void** current;
void** end;
void* pc;
};
inline _Unwind_Reason_Code unwindCallback(struct _Unwind_Context* context, void* arg)
{
BacktraceState* state = static_cast<BacktraceState*>(arg);
state->pc = (void*)_Unwind_GetIP(context);
if (state->pc)
{
if (state->current == state->end)
return _URC_END_OF_STACK;
else
*state->current++ = reinterpret_cast<void*>(state->pc);
}
return _URC_NO_REASON;
}
inline size_t captureBacktrace(void** addrs, size_t max, unsigned long pc)
{
BacktraceState state = {addrs, addrs + max, (void*)pc};
_Unwind_Backtrace(unwindCallback, &state);
personality_routine();
return state.current - addrs;
}
inline void dumpBacktrace(std::ostream& os, void** addrs, size_t count)
{
for (size_t idx = 0; idx < count; ++idx) {
const void* addr = addrs[idx];
const char* symbol = "";
Dl_info info;
if (dladdr(addr, &info) && info.dli_sname) {
symbol = info.dli_sname;
}
int status = -3;
char * demangledName = abi::__cxa_demangle(symbol, 0, 0, &status);
os << "#" << idx << ": " << addr << " " << (status == 0 ? demangledName : symbol) << "\n";
free(demangledName);
}
}
void signalHandler(int sig, siginfo_t *siginfo, void *uctx)
{
ucontext * context = (ucontext*)uctx;
unsigned long PC = context->uc_mcontext.arm_pc;
unsigned long SP = context->uc_mcontext.arm_sp;
Logger() << __PRETTY_FUNCTION__ << "Fatal signal:" << sig;
const size_t maxNumAddresses = 50;
void* addresses[maxNumAddresses];
std::ostringstream oss;
const size_t actualNumAddresses = captureBacktrace(addresses, maxNumAddresses, PC);
dumpBacktrace(oss, addresses, actualNumAddresses);
Logger() << oss.str();
exit(EXIT_FAILURE);
}
Problem: if I get the PC register by calling _Unwind_GetIP(context) in unwindCallback, I get the complete trace for the signal handler stack. Which is a separate stack, and that's obviously not what I want. So I tried supplying the PC taken from the ucontext in signal handler, and got a weird result: I get one stack entry, it is the correct entry - the function which caused the signal in the first place. But it's logged twice (even the address is the same, so it's not a symbolic name look up bug). Obviously, that's not good enough - I need the whole stack. And I wonder if this result is merely accidental (i. e. it shouldn't work in general.
Now, I read I need to also supply the stack pointer, which I apparently can get from ucontext, same as PC. But I don't know what to do with it. Do I have to unwind manually instead of using _Unwind_Backtrace? If so, can you give me sample code? I've been searching for the better part of a day, and still couldn't find anything I could copy and paste into my project.
For what it's worth, here's the libunwind source which contains _Unwind_Backtrace definition. Thought I could figure something out if I see its source, but it's way more complicated than I expected.
In order to to get stacktrace of code which caused SIGSEGV instead of stacktrace of the signal handler, you have to get ARM registers from ucontext_t and use them for unwinding.
But it is hard to do with _Unwind_Backtrace(). Thus, if you use libc++ (LLVM STL) and compile for 32-bit ARM, better try precompiled libunwind, bundled with modern Android NDKs (at sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libunwind.a). Here is a sample code.
// This method can only be used on 32-bit ARM with libc++ (LLVM STL).
// Android NDK r16b contains "libunwind.a" for armeabi-v7a ABI.
// This library is even silently linked in by the ndk-build,
// so we don't have to add it manually in "Android.mk".
// We can use this library, but we need matching headers,
// namely "libunwind.h" and "__libunwind_config.h".
// For NDK r16b, the headers can be fetched here:
// https://android.googlesource.com/platform/external/libunwind_llvm/+/ndk-r16/include/
#if _LIBCPP_VERSION && __has_include("libunwind.h")
#include "libunwind.h"
#endif
struct BacktraceState {
const ucontext_t* signal_ucontext;
size_t address_count = 0;
static const size_t address_count_max = 30;
uintptr_t addresses[address_count_max] = {};
BacktraceState(const ucontext_t* ucontext) : signal_ucontext(ucontext) {}
bool AddAddress(uintptr_t ip) {
// No more space in the storage. Fail.
if (address_count >= address_count_max)
return false;
// Reset the Thumb bit, if it is set.
const uintptr_t thumb_bit = 1;
ip &= ~thumb_bit;
// Ignore null addresses.
if (ip == 0)
return true;
// Finally add the address to the storage.
addresses[address_count++] = ip;
return true;
}
};
void CaptureBacktraceUsingLibUnwind(BacktraceState* state) {
assert(state);
// Initialize unw_context and unw_cursor.
unw_context_t unw_context = {};
unw_getcontext(&unw_context);
unw_cursor_t unw_cursor = {};
unw_init_local(&unw_cursor, &unw_context);
// Get more contexts.
const ucontext_t* signal_ucontext = state->signal_ucontext;
assert(signal_ucontext);
const sigcontext* signal_mcontext = &(signal_ucontext->uc_mcontext);
assert(signal_mcontext);
// Set registers.
unw_set_reg(&unw_cursor, UNW_ARM_R0, signal_mcontext->arm_r0);
unw_set_reg(&unw_cursor, UNW_ARM_R1, signal_mcontext->arm_r1);
unw_set_reg(&unw_cursor, UNW_ARM_R2, signal_mcontext->arm_r2);
unw_set_reg(&unw_cursor, UNW_ARM_R3, signal_mcontext->arm_r3);
unw_set_reg(&unw_cursor, UNW_ARM_R4, signal_mcontext->arm_r4);
unw_set_reg(&unw_cursor, UNW_ARM_R5, signal_mcontext->arm_r5);
unw_set_reg(&unw_cursor, UNW_ARM_R6, signal_mcontext->arm_r6);
unw_set_reg(&unw_cursor, UNW_ARM_R7, signal_mcontext->arm_r7);
unw_set_reg(&unw_cursor, UNW_ARM_R8, signal_mcontext->arm_r8);
unw_set_reg(&unw_cursor, UNW_ARM_R9, signal_mcontext->arm_r9);
unw_set_reg(&unw_cursor, UNW_ARM_R10, signal_mcontext->arm_r10);
unw_set_reg(&unw_cursor, UNW_ARM_R11, signal_mcontext->arm_fp);
unw_set_reg(&unw_cursor, UNW_ARM_R12, signal_mcontext->arm_ip);
unw_set_reg(&unw_cursor, UNW_ARM_R13, signal_mcontext->arm_sp);
unw_set_reg(&unw_cursor, UNW_ARM_R14, signal_mcontext->arm_lr);
unw_set_reg(&unw_cursor, UNW_ARM_R15, signal_mcontext->arm_pc);
unw_set_reg(&unw_cursor, UNW_REG_IP, signal_mcontext->arm_pc);
unw_set_reg(&unw_cursor, UNW_REG_SP, signal_mcontext->arm_sp);
// unw_step() does not return the first IP.
state->AddAddress(signal_mcontext->arm_pc);
// Unwind frames one by one, going up the frame stack.
while (unw_step(&unw_cursor) > 0) {
unw_word_t ip = 0;
unw_get_reg(&unw_cursor, UNW_REG_IP, &ip);
bool ok = state->AddAddress(ip);
if (!ok)
break;
}
}
void SigActionHandler(int sig, siginfo_t* info, void* ucontext) {
const ucontext_t* signal_ucontext = (const ucontext_t*)ucontext;
assert(signal_ucontext);
BacktraceState backtrace_state(signal_ucontext);
CaptureBacktraceUsingLibUnwind(&backtrace_state);
// Do something with the backtrace - print, save to file, etc.
}
Here is a sample backtrace testing app with 3 implemented backtracing methods, including the method shown above.
https://github.com/alexeikh/android-ndk-backtrace-test
First, you need to read the section on "async signal safe" functions:
http://man7.org/linux/man-pages/man7/signal.7.html
That's the entire set of functions that are safe to call in a signal handler. About the worst thing you can do is to call anything that calls malloc()/free() under the hood - or do it yourself.
Second, get it working outside of a signal handler first.
Third, these are probably apropos:
How to get C++ backtrace on Android
Android NDK: getting the backtrace
As part of getting unwinding through signal handlers (e.g. throwing an exception from one) working on arm-linux-eabihf I also obtained working backtraces from within a signal handler.
I'm pretty sure this is glibc-specific and therefore won't work on Android, but maybe it can be adapted or be useful for inspiration: https://github.com/mvduin/arm-signal-unwind

Segmentation fault occurs when calling function in the Pin tool

I am currently building a Pin tool which detects uninitialized reads from Linux application, based on this blog post.
You can also see the author's code from the blog.
Since this one is for Windows, I tried to create a Linux-compatible one.
But when I execute my Pin tool with application, a segmentation fault occurs.
The weird one is that the fault occurs when a function is called(the fault occurs when the pin tool is calling the function taint_get which is in the taint_define function), not because of access of uninitialized heap pointer or such points of general segmentation fault.
The point of the segmentation fault looks like this:
VOID Instruction(INS ins, VOID *v)
{
Uninit_Instruction(ins, v);
}
void Uninit_Instruction(INS ins, void* v)
{
// check if the stack pointer is altered (i.e. memory is allocated on the
// stack by subtracting an immediate from the stack pointer)
if(INS_Opcode(ins) == XED_ICLASS_SUB &&
INS_OperandReg(ins, 0) == REG_STACK_PTR &&
INS_OperandIsImmediate(ins, 1))
{
// insert call after, so we can pass the stack pointer directly
INS_InsertCall(ins, IPOINT_AFTER, (AFUNPTR)taint_undefined,
IARG_REG_VALUE,
REG_STACK_PTR,
IARG_ADDRINT, (UINT32) INS_OperandImmediate(ins, 1),
IARG_END);
}
UINT32 memOperands = INS_MemoryOperandCount(ins);
for (UINT32 memOp = 0; memOp < memOperands; memOp++)
{
if (INS_MemoryOperandIsRead(ins, memOp))
{
INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)taint_check,
IARG_INST_PTR,
IARG_MEMORYOP_EA, memOp,
IARG_MEMORYREAD_SIZE,
IARG_END);
}
if (INS_MemoryOperandIsWritten(ins, memOp))
{
INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)taint_define,
IARG_MEMORYOP_EA, memOp,
IARG_MEMORYWRITE_SIZE,
IARG_END);
}
}
}
The callback functions look like these:
// Taint this address as written
void taint_define(ADDRINT addr, UINT32 size)
{
// Debug purpose
TraceFile << "taint_define: " << addr << ", " << size << endl;
// taint the addresses as defined, pretty slow, but easiest to implement
for (UINT32 i = 0; i < size; i++)
{
//TraceFile << "taint_define_loop size: " << size << endl;
UINT32 *t = taint_get(addr + i);
TraceFile << "after taint_get" << endl;
UINT32 index = (addr + i) % 0x20000;
// define this bit
t[index / 32] |= 1 << (index % 32);
}
}
inline UINT32* taint_get(ADDRINT addr)
{
// Debug purpose
TraceFile << "taint_get: " << addr;
// allocate memory to taint these memory pages
if(taint[addr / 0x20000] == NULL) {
// we need an 16kb page to track 128k of memory
/*
taint[addr / 0x20000] = (UINT32 *) W::VirtualAlloc(NULL, 0x20000 / 8,
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
*/
taint[addr / 0x20000] = (UINT32*)malloc(0x20000/8);
}
return taint[addr / 0x20000];
}
The output looks like this:
C:Tool (or Pin) caused signal 11 at PC 0x7fcf475e08a4
segmentation fault (core dumped)
and the log is here.
Watched Image count: 0x1
WatchedImage: unread_3vars
Uninit_Image
Uninit_Image
Thread start
taint_define: 0x7fff06930d58, 0x8
I'm currently working on Fedora core 17 x86-64, gcc 4.7.2, and Pin 2.12-58423.
And, my pin tool code is attached here
I am currently building a Pin tool which detects uninitialized reads from Linux application, based on this blog post.
This doesn't really answer your question, and you may have other reasons to learn Pin tool, but ...
We've found Pin-based tools inadequate for instrumenting non-toy programs. IF your goal is to detect uninitialized memory reads, consider using Memory Sanitizer.
readb4write is 32 bit only. I don't know how are you are compiling it but even if you add -m32 it might still not work. This is what happened in my case but i am running it on Windows.
You can tell it is 32 bit only by looking for example at the comment: "// we use 0x8000 chunks of 128k to taint"
0x8000 x 128kb = 4294967296 which is the virtual range limit of 32 bit process.
On x64 you would need to cater for 48 bit addresses in taint_get method. This is still a naive implementation but so is everything else
typedef UINT64 * TTaint[0x80000];
TTaint *taintTable[0x10000] = { 0 };
inline UINT64 *taint_get(ADDRINT addr)
{
UINT64 chunkAddress = addr / 0x20000; //get number address of 128kb chunk.
UINT64 firstLevAddr = chunkAddress / 0x10000;
UINT64 secondLevelAddr = chunkAddress % 0x10000;
TTaint *taint = NULL;
if (taintTable[firstLevAddr] == NULL){
taintTable[firstLevAddr] = (TTaint*)W::VirtualAlloc(NULL, sizeof(TTaint),
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
}
taint = taintTable[firstLevAddr];
// allocate memory to taint these memory pages
if ((*taint)[secondLevelAddr ] == NULL) {
// we need an 16kb page to track 128k of memory
(*taint)[secondLevelAddr] = (UINT64 *)W::VirtualAlloc(NULL, 0x20000 / 8,
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
}
return (*taint)[secondLevelAddr];
}
Also most (if not all ) variables need to be UINT64 instead of UINT32. And 32 need to be changed to 64.
There is another problem i have not solved yet. There is a line that detects if the instruction accessing uninitialized memory belongs to the program being checked. It is unlikely that it is still valid in x64:
(ip & 0xfff00000) == 0x00400000)
I will publish the code in github if i manage to get it working.

"Access violation writing location" with file.getline? (Only in release build)

getting this error in an application written in C++ (VS 2010):
Unhandled exception at 0x77648da9 in divt.exe: 0xC0000005: Access
violation writing location 0x00000014.
it points to this function in free.c:
void __cdecl _free_base (void * pBlock)
{
int retval = 0;
if (pBlock == NULL)
return;
RTCCALLBACK(_RTC_Free_hook, (pBlock, 0));
retval = HeapFree(_crtheap, 0, pBlock);
if (retval == 0) //<-----------------------right here
{
errno = _get_errno_from_oserr(GetLastError());
}
}
Via debugging I was able to determine where its actually crashing:
void MenuState::LoadContentFromFile(char* File,std::string &Content)
{
std::string strbuf;
char buffer[1028];
std::fstream file;
file.open(File,std::ios_base::in);
if(file.fail())
{
Content = ErrorTable->GetString("W0001");
return;
}
if(file.is_open())
{
while(!file.eof())
{
file.getline(buffer,128,'\n'); // <----here
strbuf = buffer;
Content += strbuf + "\n";
}
}
file.close();
strbuf.clear();
}
It crashes on file.getline(buffer,128,'\n');
I don't understand why but it's only doing it in release build (Optimizations turned off), on debug build its working fine.
Any Ideas?
I know this is an old question, but when you encounter these sorts of issues buried deep in files such as, free.c or xmemory, you may also want to double check your project configuration. Especially when the issue pertains to only certain build configurations.
For example, in MSVC check your Project Properties > Configuration Properties > C/C++ > Code Generation > Runtime Library. Make sure it consistent for all dependencies and that it is set to a Debug/Release variant depending on the current build.
I would bet that the read prior to the read crashing the application actually failed (although I'm not quite sure why it would crash). The important thing to note is that eof() is only good for determining what caused a read failure (and typically suppressing an error message). In addition, you always want to check after the read whether it was successful. Finally, I can't see any reason why you don't read an std::string directly. In summary, try to use this loop instead:
for (std::string strbuf; std::getline(file, strbuf); ) {
Content += strbuf;
}
Asked a friend for help, we came up with this Solution:
std::string strbuf;
char buffer[256] = "\0";
FILE* f = fopen(File, "rt");
while(fgets(buffer,sizeof(buffer),f) != NULL)
{
Content += buffer;
}
fclose(f);
strbuf.clear();
Works fine, still thanks for your efforts.

How to programatically get uid from pid in osx using c++?

Given a pid, I want to find the owner of the process (as uid). Is there a way to get this in osx (or any unix) using C++?
Google didn't help. 'ps' is able to do it; so I assume there should be a way to get it programatically.
Solution from Indhu helped me on my way, so I would like to post my own.
UID from PID with pure C:
#include <sys/sysctl.h>
uid_t uidFromPid(pid_t pid)
{
uid_t uid = -1;
struct kinfo_proc process;
size_t procBufferSize = sizeof(process);
// Compose search path for sysctl. Here you can specify PID directly.
const u_int pathLenth = 4;
int path[pathLenth] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
int sysctlResult = sysctl(path, pathLenth, &process, &procBufferSize, NULL, 0);
// If sysctl did not fail and process with PID available - take UID.
if ((sysctlResult == 0) && (procBufferSize != 0))
{
uid = process.kp_eproc.e_ucred.cr_uid;
}
return uid;
}
No excess allocation, no loops.
The source for the ps command, reveals that there is a function called get_proc_stats defined in proc/readproc.h that (among other things) returns the real user name(UID) & Effective user name(EUID) for a given pid.
You need to do install libproc-dev to get this function. and then you can do:
#include <proc/readproc.h>
void printppid(pid_t pid)
{
proc_t process_info;
get_proc_stats(pid, &process_info);
printf("Real user of the process[%d] is [%s]\n", pid, process_info.ruser);
}
compile it with gcc the-file.c -lproc.
Once you have the real user name you can use getpwnam() and getgrnam() functions to get the uid.
You could look at how ps does it. It looks like it uses the kvm_getprocs function.
However, it's much more portable (you said "any unix", but e.g. the Linux and Solaris way is to look in the /proc filesystem - and other unixes may have different APIs) to just parse the output of ps (ps -o user= -p (pid) for example, to eliminate any extraneous output) than to do any system-specific process stuff
There's not a portable way to do this. On Mac OS, you've got to use poorly documented sysctl interfaces: see this previous stackoverflow question. (As other commenters pointed out, on Linux you can use proc. On FreeBSD, you should be able to use kvm_getfiles, although this is not available on Mac OS.)
Your best bet is to use the source for Apple's ps as a jumping-off point for grabbing process data and then you'll be able to use getpwuid(3) once you have the uid.
Finally found a way to programatically do this without parsing the output of 'ps'
uint getUidUsingSysctl(uint pid)
{
struct kinfo_proc *sProcesses = NULL, *sNewProcesses;
int aiNames[4];
size_t iNamesLength;
int i, iRetCode, iNumProcs;
size_t iSize;
iSize = 0;
aiNames[0] = CTL_KERN;
aiNames[1] = KERN_PROC;
aiNames[2] = KERN_PROC_ALL;
aiNames[3] = 0;
iNamesLength = 3;
iRetCode = sysctl(aiNames, iNamesLength, NULL, &iSize, NULL, 0);
/* allocate memory and populate info in the processes structure */
do
{
iSize += iSize / 10;
sNewProcesses = (kinfo_proc *)realloc(sProcesses, iSize);
if (sNewProcesses == 0)
{
if (sProcesses)
free(sProcesses);
/* could not realloc memory, just return */
return -1;
}
sProcesses = sNewProcesses;
iRetCode = sysctl(aiNames, iNamesLength, sProcesses, &iSize, NULL, 0);
} while (iRetCode == -1 && errno == ENOMEM);
iNumProcs = iSize / sizeof(struct kinfo_proc);
for (i = 0; i < iNumProcs; i++)
{
if (sProcesses[i].kp_proc.p_pid == pid)
{
return sProcesses[i].kp_eproc.e_ucred.cr_uid;
}
}
/* clean up and return to the caller */
free(sProcesses);
return -1;
}
Note: There might be a better way to get 'kinfo_proc' instead of iterating through all process.