Uses For Multiple Levels of Pointer Dereferences

Uses for multiple levels of pointer dereferences?

each star should be read as "which pointed to by a pointer" so

char *foo;

is "char which pointed to by a pointer foo". However

char *** foo;

is "char which pointed to by a pointer which is pointed to a pointer which is pointed to a pointer foo". Thus foo is a pointer. At that address is a second pointer. At the address pointed to by that is a third pointer. Dereferencing the third pointer results in a char. If that's all there is to it, its hard to make much of a case for that.

Its still possible to get some useful work done, though. Imagine we're writing a substitute for bash, or some other process control program. We want to manage our processes' invocations in an object oriented way...

struct invocation {
char* command; // command to invoke the subprocess
char* path; // path to executable
char** env; // environment variables passed to the subprocess
...
}

But we want to do something fancy. We want to have a way to browse all of the different sets of environment variables as seen by each subprocess. to do that, we gather each set of env members from the invocation instances into an array env_list and pass it to the function that deals with that:

void browse_env(size_t envc, char*** env_list);

How to dereference a dynamic multiple level pointer?

Using reflection you can do this:

rt := reflect.ValueOf(i).Type()
for rt.Kind() == reflect.Ptr {
rt = rt.Elem()
}

// rt is non-pointer type

https://play.golang.com/p/YbT1p4R3_1u

C triple dereferencing

Three dimensional arrays.

Dynamic dereference of a n-level pointer

First of all: Do you really need a jagged array? Do you want to have some sort of sparse array? Because otherwise, could you not just flatten your n-dimensional structure into a single, long array? That would not just lead to much simpler code, but most likely also be more efficient.

That being said: It can be done for sure. For example, just use a recursive template and rely on overloading to peel off levels of indirection until you get to the bottom:

template <typename T>
void save_array(T* arr, unsigned int* dimensions)
{
for (unsigned int i = 0U; i < *dimensions; ++i)
std::cout << ' ' << *arr++;
std::cout << std::endl;
}

template <typename T>
void save_array(T** arr, unsigned int* dimensions)
{
for (unsigned int i = 0U; i < *dimensions; ++i)
save_array(*arr, dimensions + 1);
}

You don't even need to explicitly specify the number of indirections n, since that number is implicitly given by the pointer type.

You can do basically the same trick to allocate/deallocate the array too:

template <typename T>
struct array_builder;

template <typename T>
struct array_builder<T*>
{
T* allocate(unsigned int* dimensions) const
{
return new T[*dimensions];
}
};

template <typename T>
struct array_builder<T**> : private array_builder<T*>
{
T** allocate(unsigned int* dimensions) const
{
T** array = new T*[*dimensions];

for (unsigned int i = 0U; i < *dimensions; ++i)
array[i] = array_builder<T*>::allocate(dimensions + 1);

return array;
}
};

Just this way around, you need partial specialization since the approach using overloading only works when the type can be inferred from a parameter. Since functions cannot be partially specialized, you have to wrap it in a class template like that. Usage:

unsigned int dim[4] = { 50, 60, 80, 50 };
auto arr = array_builder<std::uint8_t****>{}.allocate(dim);
arr[0][0][0][0] = 42;
save_array(arr, dim);

Hope I didn't overlook anything; having this many indirections out in the open can get massively confusing real quick, which is why I strongly advise against ever doing this in real code unless absolutely unavoidable. Also this raw usage of new all over the place is anything but great. Ideally, you'd be using, e.g., std::unique_ptr. Or, better yet, just nested std::vectors as suggested in the comments…

How does println! interact with multiple levels of indirection?

Let's start with a trick question: Does this compile or not?

fn main() {
println!("{:p}", 1i32);
}

We're asking to print an i32 as a memory address. Does this make sense?

No, of course, and Rust rightfully rejects this program.

error[E0277]: the trait bound `i32: std::fmt::Pointer` is not satisfied
--> src/main.rs:2:22
|
2 | println!("{:p}", 1i32);
| ^^^^ the trait `std::fmt::Pointer` is not implemented for `i32`
|
= note: required by `std::fmt::Pointer::fmt`
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

But we know that the macro implicitly borrow the arguments, so 1i32 becomes &1i32. And references do implement Pointer. So what's the deal?

First, it helps to understand why the macro borrows its arguments. Have you ever noticed that all the formatting traits look virtually identical? They all define exactly one method, named fmt, that takes two parameters, &self and a &mut Formatter and returns Result<(), fmt::Error>.

It is the &self that is relevant here. In order to call fmt, we only need a reference to the value, because formatting a value doesn't need ownership of that value. Now, the implementation of formatting arguments is more complicated than this, but ultimately, for an argument x, the program would end up calling std::fmt::Pointer::fmt(&x, formatter) (for :p). However, for this call to compile successfully, the type of x must implement Pointer, not the type of &x. If x is 1i32, then the type of x is i32, and i32 doesn't implement Pointer.

The conclusion is that the :p format will end up printing the value of the pointer represented by the expression written textually in your program. The borrow taken on that expression is there so that the macro doesn't take ownership of the argument (which is still useful for :p, e.g. if you wanted to print a Box<T>).


Now we can proceed to explaining the behavior of your program. x is a local variable. Local variables usually1 have a stable address2. In your Example 1 calls, the expression &x allows us to observe that address. Both occurrences of &x will give the same result because x hasn't moved between the calls. What's printed is the address of x (i.e. the address that holds the value 0).

However, the expression &&x is a bit curious. What does it mean exactly to take the address twice? The subexpression &x produces a temporary value, because the result is not assigned to a variable. Then, we ask the address of that temporary value. Rust is kind enough to let us do that, but that means we must store the temporary value somewhere in memory in order for it to have some address. Here, the temporary value is stored in some hidden local variable.

It turns out that in debug builds, the compiler creates a separate hidden variable for each of the &x subexpressions in the two occurrences of &&x. That's why we can observe two different memory addresses for the Example 2 lines. However, in release builds, the code is optimized so that only one hidden variable is created (because at the point where we need the second one, we no longer need the first one, so we can reuse its memory location), so the two Example 2 lines actually print the same memory address!


1 I say usually because there might be situations where an optimizer could decide to move a local variable around in memory. I don't know if any optimizer actually does that in practice.

2 Some local variables might not have an "address" at all! An optimizer may decide to keep a local variable in a register instead if the address of that variable is never observed. On many processor architectures, registers cannot be addressed by a pointer, because they live in a different "address space", so to speak. Of course, here, we are observing the address, so we can be pretty confident that the variable actually lives on the stack.

Double pointer dereferences in a function

At the point you have **min = 3 the value of *min is 0, i.e. a NULL pointer, which means it doesn't point anywhere. Attempting to dereference *min and subsequently write to it invokes undefined behavior.

The following lines set both *min and *max to point to the same place as start, so after that they can be dereferenced.

What is the cleanest way to dereference multi-level pointers with offsets?

I'm not sure about "cleanest way", but there's nothing you can do except to dereference those pointers. I suggest using typedefs just to make your code more readable.

Also don't worry about casting int * to int **. Sure it's considered "bad practice", but if you know what you're doing it might be exactly what is required. You just have to be careful.

typedef int *** intPtr3;
typedef int ****** intPtr6;

You can also use some macros to clean up your syntax. This would be a good example of intelligently using a macro to benefit readability and cleanliness, and reduce chance of errors:

#define DEREF6( PTR ) \
******(PTR)

Lastly there's a nice macro I use quite often for moving a pointer an amount of bytes in memory:

#define PTR_ADD( PTR, OFFSET ) \
(((char *)(PTR)) + (OFFSET))

WriteProcessMemory with Multi Level Pointer

Your offsets are added in the wrong order. You need to do from bottom to top of that screenshot.

You need to de-reference each pointer in the chain, you're not doing that.

Here is how to correctly do it:

DWORD GetProcId(const wchar_t* procName)
{
DWORD procId = 0;
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnap != INVALID_HANDLE_VALUE)
{
PROCESSENTRY32 procEntry;
procEntry.dwSize = sizeof(procEntry);

if (Process32First(hSnap, &procEntry))
{
do
{
if (!_wcsicmp(procEntry.szExeFile, procName))
{
procId = procEntry.th32ProcessID;
break;
}
} while (Process32Next(hSnap, &procEntry));

}
}
CloseHandle(hSnap);
return procId;
}

uintptr_t GetModuleBaseAddress(DWORD procId, const wchar_t* modName)
{
uintptr_t modBaseAddr = 0;
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, procId);
if (hSnap != INVALID_HANDLE_VALUE)
{
MODULEENTRY32 modEntry;
modEntry.dwSize = sizeof(modEntry);
if (Module32First(hSnap, &modEntry))
{
do
{
if (!_wcsicmp(modEntry.szModule, modName))
{
modBaseAddr = (uintptr_t)modEntry.modBaseAddr;
break;
}
} while (Module32Next(hSnap, &modEntry));
}
}
CloseHandle(hSnap);
return modBaseAddr;
}

uintptr_t FindDMAAddy(HANDLE hProc, uintptr_t ptr, std::vector<unsigned int> offsets)
{
uintptr_t addr = ptr;
for (unsigned int i = 0; i < offsets.size(); ++i)
{
ReadProcessMemory(hProc, (BYTE*)addr, &addr, sizeof(addr), 0);
addr += offsets[i];
}
return addr;
}
int main()
{
//Get ProcId of the target process
DWORD procId = GetProcId(L"Insaniquarium.exe");

//Getmodulebaseaddress
uintptr_t moduleBase = GetModuleBaseAddress(procId, L"Insaniquarium.exe");

//Get Handle to Process
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, NULL, procId);

//Resolve base address of the pointer chain
uintptr_t dynamicPtrBaseAddr = moduleBase + 0x001E8AA0;

//Resolve the pointer chain
std::vector<unsigned int> offsets = {0x68, 0x104, 0x4, 0x62C, 0x3F0};

uintptr_t addr = FindDMAAddy(hProcess, dynamicPtrBaseAddr, offsets);

return 0;
}


Related Topics



Leave a reply



Submit