0

I was wondering if there would be a convenient way to copy the current stack frame, move it somewhere else, and then 'return' from the function, from the new location?

I have been playing around with setjmp and longjmp while allocating large arrays on the stack to force the stack pointer away. I am familiar with the calling conventions and where arguments to functions end up etc, but I am not extremely experienced with pointer arithmetic.

To describe the end goal in general terms; The ambition is to be able to allocate stack frames and to jump to another stack frame when I call a function (we can call this function switch). Before I jump to the new stack frame, however, I'd like to be able to grab the return address from switch so when I've (presumably) longjmpd to the new frame, I'd be able to return to the position that initiated the context switch.

I've already gotten some inspiration of how to imitate coroutines using longjmp an setjmp from this post.

If this is possible, it would be a component of my current research, where I am trying to implement a (very rough) proof of concept extension in a compiler. I'd appreciate answers and comments that address the question posed in my first paragraph, only.

Update

To try and make my intention clearer, I wrote up this example in C. It needs to be compiled with -fno-stack-protector. What i want is for the local variables a and b in main to not be next to each other on the stack (1), but rather be separated by a distance specified by the buffer in call. Furthermore, currently this code will return to main twice, while I only want it to do so once (2). I suggest you read the procedures in this order: main, call and change.

If anyone could answer any of the two question posed in the paragraph above, I would be immensely grateful. It does not have to be pretty or portable.

Again, I'd prefer answers to my questions rather than suggestions of better ways to go about things.

#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>

jmp_buf* buf;
long* retaddr;

int change(void) {
  // local variable to use when computing offsets
  long a[0];

  for(int i = 0; i < 5; i++) a[i]; // same as below, not sure why I need to read this

  // save this context
  if(setjmp(*buf) == 0) {
    return 1;
  }
  // the following code runs when longjmp was called with *buf

  // overwrite this contexts return address with the one used by call
  a[2] = *retaddr;

  // return, hopefully now to main
  return 1;
}

static void* retain;

int call() {
  buf     = (jmp_buf*)malloc(sizeof(jmp_buf));
  retaddr = (long*)   malloc(sizeof(long));
  long a[0];
  for(int i = 0; i < 5; i++) a[i]; // not sure why I need to do this. a[2] reads (nil) otherwise

  // store return address
  *retaddr = a[2];

  // allocate local variables to move the stackpointer
  char n[1024];
  retain = n; // maybe cheat the optimiser?

  // get a jmp_buf from another context
  change();

  // jump there
  longjmp(*buf, 1);
}


// It returns to main twice, I am not sure why
int main(void) {
  char a;
  call(); // this function should move stackpointer (in this case, 1024 bytes)
  char b;
  printf("address of a: %p\n", &a);
  printf("address of b: %p\n", &b);
  return 1;
}
Community
  • 1
  • 1
Robert
  • 277
  • 1
  • 16
  • 1
    IEnumerable from c# in c ! I think it's compiler/platform dependant, good luck with that one. – Orace Dec 16 '19 at 08:06
  • 4
    This is possible, it is what multi-tasking schedulers do, e.g. in embedded environments. It is however extremely environment specific and would have to dig into the the specifics of the processor it is running on. Determine the registers which contain the needed information. Find out how their content can be stored (most likely assembler specific instructions). Use them to store all contents contiguosly. The place to do so is probably allocated already, inside the object describing and administrating the current task. I don't think that pointer arithmetic is the most relevant tool here. – Yunnosch Dec 16 '19 at 08:11
  • Please excuse the slightly pessimistic tone of the this comment. But a proof of concept of something which is already generally used does not seem to be a promising research. The only difference I see is that what is used is usually some software (called operating system, which is of course only note-worthy on embedded environments...) which is compiled by normal compilers - instead of the compiler extension you describe. This makes the software much easier to distribute/sell; even if sometimes still is compiler-specific. – Yunnosch Dec 16 '19 at 08:18
  • @yunnosch What I am describing is not the research topic. The topic is related to region-based memory management and concurrency. – Robert Dec 16 '19 at 08:28
  • Ah, good for you. Still, what you describe seems to be a multitasking scheduler. Can you use one (e.g. by getting a license for an existing Os)? It seems it would save you a lot of effort. These things are of course non-trivial to implement. – Yunnosch Dec 16 '19 at 08:31
  • Also, please let me know how far off my first, less-pessimistic, comment is. I would like to turn it into an answer - if it is at least tangentially helpful. Maybe point out what is missing for you. – Yunnosch Dec 16 '19 at 08:33
  • @Yunnosch The compiler I am modifying makes heavy use of the system stack, but offers limited support to interact with it. As my time on this project is running out I'd like to leave it with a proof of concept implementation, to provide SOME numbers. It does not have to be portable or pretty. – Robert Dec 16 '19 at 08:35
  • That is obvious. I stay with my first comment. – Yunnosch Dec 16 '19 at 08:36
  • Another pessimistic one.... If time is running out at this stage I feel with you. – Yunnosch Dec 16 '19 at 08:37
  • @Yunnosch Your first answer is descriptive and explains the situation, but does not tell me much more than I already know at this point. It is surely of value to other people that might find their way to this question, but not so much for me at this very moment, sadly. – Robert Dec 16 '19 at 08:38
  • Could you [edit] your question to provide more about what you currently need? Maybe (implicitly) contrast it against my comment. That would get the question clearer and more easy to answer. I would like to go into the right kind of details, but do not see which those are. I still have e.g. the impression that you are on the wrong track with thinking of pointer arithmetic. The content of the registers which make the stack frame are in registers, not anywhere in memory which a pointer can point to. (At least in most current systems, C64 staying out of this....). – Yunnosch Dec 16 '19 at 08:40
  • What is it that you really need? You talk about copying the stack frame, which is possible though not in a portable way and (probably) not in straight C. But why do you need to copy it? Are you just trying to gain control after a function call returns or do you need to preserve the content of the original stack frame somehow? – 500 - Internal Server Error Dec 16 '19 at 09:58
  • @500-InternalServerError The general purpose is to move the stack pointer, such that after all this has happened the stack pointer is at another location (a specific offset from the original SP). – Robert Dec 16 '19 at 10:07
  • @RobertK that doesn't make any sense, the stack can contain pointers to the stack... and how'd you think they would work? – Antti Haapala -- Слава Україні Dec 16 '19 at 10:24
  • @RobertK: - which will allow you to accomplish what exactly? (perhaps there's a more straight-forward way to achieve your end goal). – 500 - Internal Server Error Dec 16 '19 at 10:33
  • @500-InternalServerError I can't read the OPs mind, but one application would be to randomize the stack frame. For the most part, the stack frame behaves very much like a structure, and can be subject to targeted attacks like buffer overflows. If I can dynamically insert a random amount of space between local variables, I have made an attackers job a lot harder. Arguably more effective than aslr and the like. – mevets Dec 17 '19 at 01:06

2 Answers2

0

This is possible, it is what multi-tasking schedulers do, e.g. in embedded environments.
It is however extremely environment-specific and would have to dig into the the specifics of the processor it is running on.

Basically, the possible steps are:

  • Determine the registers which contain the needed information. Pick them by what you need, they are probably different from what the compiler uses on the stack for implementing function calls.
  • Find out how their content can be stored (most likely specific assembler instructions for each register).
  • Use them to store all contents contiguosly.
  • The place to do so is probably allocated already, inside the object describing and administrating the current task.
  • Consider not using a return address. Instead, when done with the "inserted" task, decide among the multiple task datasets which describe potential tasks to return to. That is the core of scheduling. If the return address is known in advance, then it is very similar to normal function calling. I.e. the idea is to potentially return to a different task than the last one left. That is also the reason why tasks need their own stack in many cases.

By the way, I don't think that pointer arithmetic is the most relevant tool here.
The content of the registers which make the stack frame are in registers, not anywhere in memory which a pointer can point to. (At least in most current systems, C64 staying out of this....).

Yunnosch
  • 26,130
  • 9
  • 42
  • 54
  • ... and how about the SSE12345 AVX FPU etc registers - it is open-ended set... – Antti Haapala -- Слава Україні Dec 16 '19 at 10:20
  • 1
    @AnttiHaapala I don't exactly get your point. I think those are part of the "extremely environment-specific" issue I mentioned. But I guess you mean something more. Maybe that the registers which need to be saved, and even their number (my interpretation of your "open-ended set" expression), is unpredictable, even if the code is targeted at the current environment? Please elaborate. – Yunnosch Dec 16 '19 at 12:11
0

tl;dr - no.

(On every compiler worth considering): The compiler knows the address of local variables by their offset from either the sp, or a designated saved stack pointer, the frame or base pointer. a might have an address of (sp+1), and b might have an address of (sp+0). If you manage to successfully return to main with the stack pointer lowered by 1024; these will still be known as (sp+1), (sp+0); although they are technically now (sp+1-1024), (sp+0-1024), which means they are no longer a & b.

You could design a language which fixed the local allocation in the way you consider, and that might have some interesting expressiveness, but it isn't C. I doubt any existing compiler could come up with a consistent handling of this. To do so, when it encountered:

char a;

it would have to make an alias of this address at the point it encountered it; say:

add %sp, $0, %r1
sub %sp, $1, %sp

and when it encountered

char b;
add %sp, $0, %r2
sub %sp, $1, %sp

and so on, but one it runs out of free registers, it needs to spill them on the stack; and because it considers the stack to change without notice, it would have to allocate a pointer to this spill area, and keep that stored in a register.

Btw, this is not far removed from the concept of a splayed stack (golang uses these), but generally the granularity is at a function or method boundary, not between two variable definitions.

Interesting idea though.

mevets
  • 10,070
  • 1
  • 21
  • 33