I'm currently learning assembly to have a better understanding of coding but there is something I can't fully wrap my head around I'd really appreciate some help.
sometimes in the code I see:
...
add edx, 1
call relative_offset_to_current_ip
...
now I can follow the jump and inspect the code but sometimes I also see
...
add edx,1
call ebx
...
so I can't understand where the really code is until I add a debugger.
I'd kindly ask to help me on the following question:
Does the "register calling version" also have a relative address to the current IP?
Why is it put in a register? Shouldn't the routine code that is about to be called already be laying in the code section waiting?
Can calls made via register even be traced without a debugger?
Does the "register calling version" also have a relative address to the current IP?
When a target address is provided as data, it is the memory address of the first instruction of some function. It can be passed as a parameter, used here or there. The caller using a function pointer should just use that pointer unmodified or else you won't get to the first instruction of the function. The same function pointer (a pointer, data) can be invoked from multiple different locations, but should still transfer control to the first instruction of the function.
By contrast, different calls to the same function that are done using pc-relative addressing are each independently adjusted to point to the first instruction of the function. This independent adjustment is both not possible and unnecessary when calling through a pointer.
However, all forms of call obtain a return address (and provide it to the function to use at its return) that is indeed relative to the call site (but the offset used there is merely the size of the particular call instruction; there is no pc-relative offset encoded in the instruction for that).
Why is it put in a register?
Most languages allow first-class functions, which is to say that we can have a variable or parameter that refers to a function. When the user defines a function pointer, possibly as an argument to a function, and then invokes that function referred to by the function pointer, an indirect call is required. A call register instruction is helpful but not strictly necessary, as the effect of indirect call can be composed by generating a return address, putting that in the right place, and then using an indirect jump.
Dynamic linked libraries may use function pointers invisible to the programmer (unless debugging in assembly/machine code). This can happen on some systems when the location of some code is unknown until runtime, and thus supplied as data — essentially a function pointer that remains constant once identified (when the dynamically loaded library is actually loaded). It can also happen if the target is too far away in address for a pc-relative call (how far is too far varies across instruction sets).
Virtual method dispatch in object oriented languages uses function pointers invisible to the programmer (in their OO language). The runtimes for these OO languages maintain a set of tables of function pointers, where the specific object (receiving the message) determines which table to consult for the appropriate function pointer to invoke the proper virtual method.
Shouldn't the routine code that is about to be called already be laying in the code section waiting?
Typically, yes, but for the above reasons, the program (at such point) only knows where using data (function pointers).
Can calls made via register even be traced without a debugger?
I don't know what you mean by tracing if you are not debugging — for example, one kind of tracing is single stepping in the debugger.
On the other hand, the control flow is not random, and in some sense predictable by the programmer, if they know what the code is supposed to do.