GenASM: Make sure the stack is 16 byte aligned before calling malloc or free.

The GenASM visitor only uses quad mnemonics so each manipulation to the stack is done in 8 byte chunks. This means our stack is always 8 byte aligned which is fine when only calling KLang code. libc functions like 'malloc' or 'free' require the stack to be 16 byte aligned which isn't automatically guaranteed when being 8 byte aligned. This is why we need to be careful when manipulating the stack (e.g. pushq, popq, subq x, %rsp). By assuming that we are stack aligned at the beginning of execution and by tracking the number of pushq operations we can deduce whether we misaligned the stack and align it again by adding another 8 byte to the stack.

This re-alignment of the stack is done directly before entering a function body or calling malloc. Since there are no stack manipulations involved when generating the code to calling 'free' there is reason to check for alignment there.
This commit is contained in:
2023-03-21 00:46:38 +01:00
parent f38bd3d69e
commit bacc40d844
4 changed files with 161 additions and 64 deletions

View File

@@ -71,48 +71,12 @@ public class GenASM implements Visitor<Void> {
private int currentFunctionStartLabel = 0;
private Parameter[] currentFunctionParams;
private boolean prepareRegisters(Expression lhs, Expression rhs) {
boolean lhsIsFloat = lhs.type.equals(Type.getFloatType());
boolean rhsIsFloat = rhs.type.equals(Type.getFloatType());
if (lhsIsFloat && rhsIsFloat) {
lhs.welcome(this);
asm.mov("sd", "%xmm0", "%xmm2");
rhs.welcome(this);
asm.mov("sd", "%xmm2", "%xmm0");
asm.mov("sd", "%xmm2", "%xmm0");
return true;
} else if (lhsIsFloat && !rhsIsFloat) {
lhs.welcome(this);
rhs.welcome(this);
asm.cvtsi2sd("%rax", "%xmm1");
return true;
} else if (!lhsIsFloat && rhsIsFloat) {
lhs.welcome(this);
asm.cvtsi2sd("%rax", "%xmm2");
rhs.welcome(this);
asm.mov("sd", "%xmm0", "%xmm1");
asm.mov("sd", "%xmm2", "%xmm0");
return true;
} else {
lhs.welcome(this);
asm.push("q", "%rax");
rhs.welcome(this);
asm.mov("q", "%rax", "%rbx");
asm.pop("q", "%rax");
return false;
}
}
public GenASM(String mainName, Map<String, StructDefinition> structs) {
this.mainName = mainName;
this.structs = structs;
this.asm = new ASM();
}
public GenASM(Map<String, StructDefinition> structs) {
this("main", structs);
}
public String toAsm() {
return asm.toAsm();
}
@@ -573,32 +537,32 @@ public class GenASM implements Visitor<Void> {
e.name = "main_by_user";
}
int lblStart = ++lCount;
this.currentFunctionStartLabel = lblStart;
this.currentFunctionParams = e.parameters;
var lblStart = ++lCount;
currentFunctionStartLabel = lblStart;
currentFunctionParams = e.parameters;
asm.functionHead(e.name);
asm.push("q", "%rbp");
asm.mov("q", "%rsp", "%rbp");
asm.label(lblStart);
// hole die anzahl der lokalen variablen
this.vars = new TreeSet<String>();
HashMap<String, Type> types = new HashMap<String, Type>();
GetVars getvars = new GetVars(vars, types);
getvars.visit(e);
// Get the number of local variables.
// TODO: Do this during context analysis and save the result as an attribute of FunctionDefinition.
vars = new TreeSet<>();
new GetVars(vars, new HashMap<>()).visit(e);
// Erzeuge ein environment
this.env = new HashMap<String, Integer>();
// Create a new environment
env = new HashMap<>();
// Merke dir die offsets der parameter, die direkt auf den stack gelegt wurden
int offset = 16; // Per Stack übergebene Parameter liegen über unserem BSP
int ri = 0;
int fi = 0;
// Per stack übergebene variablen in env registrieren
// Remember the offsets of the arguments that were placed on the stack.
var offset = 16; // Variables that were passed via the stack are located above our BSP
var ri = 0;
var fi = 0;
// Add arguments that were passed via the stack to the environment
var registerParameters = new ArrayList<Parameter>();
for (int i = 0; i < e.parameters.length; i++) {
if (e.parameters[i].type.equals(Type.getFloatType())) {
if (fi >= this.floatRegisters.length) {
if (fi >= floatRegisters.length) {
// parameter is on stack already
env.put(e.parameters[i].name, offset);
offset += 8;
@@ -608,7 +572,7 @@ public class GenASM implements Visitor<Void> {
fi++;
}
} else {
if (ri >= this.registers.length) {
if (ri >= registers.length) {
// parameter is on stack already
env.put(e.parameters[i].name, offset);
offset += 8;
@@ -623,26 +587,42 @@ public class GenASM implements Visitor<Void> {
offset = 0;
ri = 0;
fi = 0;
// Push arguments that were passed via registers onto the stack
// and add them to the environment.
for (var param: registerParameters) {
if (param.type.equals(Type.getFloatType())) {
asm.mov("q", this.floatRegisters[fi], "%rax");
asm.mov("q", floatRegisters[fi], "%rax");
asm.push("q", "%rax");
offset -= 8;
this.env.put(param.name, offset); // negative, liegt unter aktuellem BP
env.put(param.name, offset); // negative, liegt unter aktuellem BP
fi++;
} else {
asm.push("q", this.registers[ri]);
asm.push("q", registers[ri]);
offset -= 8;
this.env.put(param.name, offset); // negative, liegt unter aktuellem BP
env.put(param.name, offset); // negative, liegt unter aktuellem BP
ri++;
}
}
// Reserviere Platz auf dem Stack für jede lokale variable
for (String lok_var : vars) {
offset -= 8;
asm.push("q", 0);
this.env.put(lok_var, offset);
// Reserve memory on the stack for the local variables.
if (!vars.isEmpty()) {
// Each variable is at most 8 bytes in size.
asm.sub("q", "$" + (8 * vars.size()), "%rsp");
// Save the offsets (they are relative to rbp)
for (String lok_var : vars) {
offset -= 8;
this.env.put(lok_var, offset);
}
}
// Check the stack alignment and correct if necessary
if ((registerParameters.size() + vars.size()) % 2 != 0) {
// Since each variable is 8 bytes and the stack is 16 byte aligned
// we need to add 8 bytes to the stack to make it aligned
// if there is an odd number of parameters and local variables.
asm.sub("q", "$8", "%rsp");
}
e.block.welcome(this);
@@ -851,12 +831,25 @@ public class GenASM implements Visitor<Void> {
asm.push("q", "%rax");
}
// Make sure the stack is aligned before calling malloc
if (e.args.length % 2 != 0) {
// an odd number of arguments means we called pushq an odd number of times
// which results in an unaligned stack. Subtract 8 from the stack pointer to
// re-align the stack
asm.sub("q", "$8", "%rsp");
}
// allocate heap memory by calling malloc
var structDef = this.structs.get(e.structName);
asm.mov("l", Helper.getFieldSizeBytes(structDef), "%edi");
asm.call("malloc@PLT");
// push args into struct memory, last arg is ontop of the stack
// Get rid of the stack alignment if there was any
if (e.args.length % 2 != 0) {
asm.add("q", "$8", "%rsp");
}
// push args into struct memory, last arg is on top of the stack
for (int i = e.args.length - 1; i >= 0; i--) {
asm.pop("q", Helper.getFieldOffset(structDef, i) + "(%rax)");
}
@@ -910,4 +903,35 @@ public class GenASM implements Visitor<Void> {
return null;
}
private boolean prepareRegisters(Expression lhs, Expression rhs) {
boolean lhsIsFloat = lhs.type.equals(Type.getFloatType());
boolean rhsIsFloat = rhs.type.equals(Type.getFloatType());
if (lhsIsFloat && rhsIsFloat) {
lhs.welcome(this);
asm.mov("sd", "%xmm0", "%xmm2");
rhs.welcome(this);
asm.mov("sd", "%xmm2", "%xmm0");
asm.mov("sd", "%xmm2", "%xmm0");
return true;
} else if (lhsIsFloat && !rhsIsFloat) {
lhs.welcome(this);
rhs.welcome(this);
asm.cvtsi2sd("%rax", "%xmm1");
return true;
} else if (!lhsIsFloat && rhsIsFloat) {
lhs.welcome(this);
asm.cvtsi2sd("%rax", "%xmm2");
rhs.welcome(this);
asm.mov("sd", "%xmm0", "%xmm1");
asm.mov("sd", "%xmm2", "%xmm0");
return true;
} else {
lhs.welcome(this);
asm.push("q", "%rax");
rhs.welcome(this);
asm.mov("q", "%rax", "%rbx");
asm.pop("q", "%rax");
return false;
}
}
}

View File

@@ -94,6 +94,11 @@ int testStructCreation() {
struct_testExpected_l("init inner field a", 20, resultRec->b->a);
struct_testExpected_s("init inner field b", NULL, resultRec->b->b);
// The result of this test is always 1 because if the tests fail
// a segmentation fault occurs.
long alignmentResult = isStackAlignedBeforeFunctionCall();
struct_testExpected_l("stack alignment before malloc", 1, alignmentResult);
free(resultRec);
free(innerStruct);
}

View File

@@ -28,4 +28,6 @@ long getStructFieldRecA(struct testStructRec *t);
struct testStructRec *getStructFieldRecB(struct testStructRec *t);
struct testStructRec *setStructFieldRecA(struct testStructRec *t, long a);
struct testStructRec *setStructFieldRecB(struct testStructRec *t, struct testStructRec *b);
struct testStructRec *setStructFieldRecB(struct testStructRec *t, struct testStructRec *b);
long isStackAlignedBeforeFunctionCall();

View File

@@ -555,6 +555,72 @@ function getTestStructRec(a: int, b: testStructRec): testStructRec {
return create testStructRec(a, b);
}
struct evenFieldsStruct {
a: int;
b: int;
}
struct oddFieldsStruct {
a: int;
b: int;
c: int;
}
function stackAlignmentTestInternal1(): oddFieldsStruct {
// an odd amount of constructor parameters could lead to an unaligned stack
// which results in a segmentation fault when calling malloc
return create oddFieldsStruct(1, 1, 1);
}
function stackAlignmentTestInternal2(): evenFieldsStruct {
// an odd amount of local variables could lead to an unaligned stack
// which results in a segmentation fault when calling malloc
let a: evenFieldsStruct = create evenFieldsStruct(1, 1);
return a;
}
function stackAlignmentTestInternal3(a: int): evenFieldsStruct {
// an odd amount of function parameters could lead to an unaligned stack
// which results in a segmentation fault when calling malloc
return create evenFieldsStruct(1, 1);
}
function stackAlignmentTestInternal4(a: int): evenFieldsStruct {
// if (function parameters + local variables + constructor parameters) is odd
// then this could lead to an unaligned stack
// which results in a segmentation faul when calling malloc
let b: int = 0;
let c: int = 0;
return create evenFieldsStruct(1, 1);
}
function stackAlignmentTestInternal5(a: int, b: int): evenFieldsStruct {
// if (function parameters + local variables + constructor parameters) is odd
// then this could lead to an unaligned stack
// which results in a segmentation faul when calling malloc
let c: int = 0;
return create evenFieldsStruct(1, 1);
}
function stackAlignmentTestInternal6(a: int, b: int): oddFieldsStruct {
// if (function parameters + local variables + constructor parameters) is odd
// then this could lead to an unaligned stack
// which results in a segmentation faul when calling malloc
let c: int = 0;
let d: int = 0;
return create oddFieldsStruct(1, 1, 1);
}
function isStackAlignedBeforeFunctionCall(): int {
let a: oddFieldsStruct = stackAlignmentTestInternal1();
let b: evenFieldsStruct = stackAlignmentTestInternal2();
let c: evenFieldsStruct = stackAlignmentTestInternal3(1);
let d: evenFieldsStruct = stackAlignmentTestInternal4(1);
let e: evenFieldsStruct = stackAlignmentTestInternal5(1, 1);
let f: oddFieldsStruct = stackAlignmentTestInternal6(1, 1);
return 1;
}
function getStructFieldA(t: testStruct): int {
return t.a;
}