in src/util/fipstools/delocate/delocate.go [1293:1603]
func (d *delocation) processIntelInstruction(statement, instruction *node32) (*node32, error) {
assertNodeType(instruction, ruleInstructionName)
instructionName := d.contents(instruction)
argNodes := instructionArgs(instruction.next)
var wrappers wrapperStack
var args []string
changed := false
Args:
for i, arg := range argNodes {
fullArg := arg
isIndirect := false
if arg.pegRule == ruleIndirectionIndicator {
arg = arg.next
isIndirect = true
}
switch arg.pegRule {
case ruleRegisterOrConstant, ruleLocalLabelRef:
args = append(args, d.contents(fullArg))
case ruleMemoryRef:
symbol, offset, section, didChange, symbolIsLocal, memRef := d.parseMemRef(arg.up)
changed = didChange
if symbol == "OPENSSL_ia32cap_P" && section == "" {
if instructionName != "leaq" {
return nil, fmt.Errorf("non-leaq instruction %q referenced OPENSSL_ia32cap_P directly", instructionName)
}
if i != 0 || len(argNodes) != 2 || !d.isRIPRelative(memRef) || len(offset) > 0 {
return nil, fmt.Errorf("invalid OPENSSL_ia32cap_P reference in instruction %q", instructionName)
}
target := argNodes[1]
assertNodeType(target, ruleRegisterOrConstant)
reg := d.contents(target)
if !strings.HasPrefix(reg, "%r") {
return nil, fmt.Errorf("tried to load OPENSSL_ia32cap_P into %q, which is not a standard register.", reg)
}
changed = true
// Flag-altering instructions (i.e. addq) are going to be used so the
// flags need to be preserved.
wrappers = append(wrappers, saveFlags(d.output, false /* Red Zone not yet cleared */))
wrappers = append(wrappers, func(k func()) {
d.output.WriteString("\tleaq\tOPENSSL_ia32cap_addr_delta(%rip), " + reg + "\n")
d.output.WriteString("\taddq\t(" + reg + "), " + reg + "\n")
})
break Args
}
switch section {
case "":
if _, knownSymbol := d.symbols[symbol]; knownSymbol {
symbol = localTargetName(symbol)
changed = true
}
case "PLT":
if classifyInstruction(instructionName, argNodes) != instrJump {
return nil, fmt.Errorf("Cannot rewrite PLT reference for non-jump instruction %q", instructionName)
}
if _, knownSymbol := d.symbols[symbol]; knownSymbol {
symbol = localTargetName(symbol)
changed = true
} else if !symbolIsLocal && !isSynthesized(symbol) {
// Unknown symbol via PLT is an
// out-call from the module, e.g.
// memcpy.
d.redirectors[symbol+"@"+section] = redirectorName(symbol)
symbol = redirectorName(symbol)
}
changed = true
case "GOTPCREL":
if len(offset) > 0 {
return nil, errors.New("loading from GOT with offset is unsupported")
}
if !d.isRIPRelative(memRef) {
return nil, errors.New("GOT access must be IP-relative")
}
useGOT := false
if _, knownSymbol := d.symbols[symbol]; knownSymbol {
symbol = localTargetName(symbol)
changed = true
} else if !isSynthesized(symbol) {
useGOT = true
}
classification := classifyInstruction(instructionName, argNodes)
if classification != instrThreeArg && classification != instrCompare && i != 0 {
return nil, errors.New("GOT access must be source operand")
}
// Reduce the instruction to movq symbol@GOTPCREL, targetReg.
var targetReg string
var redzoneCleared bool
switch classification {
case instrPush:
wrappers = append(wrappers, push(d.output))
targetReg = "%rax"
case instrConditionalMove:
wrappers = append(wrappers, undoConditionalMove(d.output, instructionName))
fallthrough
case instrMove:
assertNodeType(argNodes[1], ruleRegisterOrConstant)
targetReg = d.contents(argNodes[1])
case instrCompare:
otherSource := d.contents(argNodes[i^1])
saveRegWrapper, tempReg := saveRegister(d.output, []string{otherSource})
redzoneCleared = true
wrappers = append(wrappers, saveRegWrapper)
if i == 0 {
wrappers = append(wrappers, compare(d.output, instructionName, tempReg, otherSource))
} else {
wrappers = append(wrappers, compare(d.output, instructionName, otherSource, tempReg))
}
targetReg = tempReg
case instrTransformingMove:
assertNodeType(argNodes[1], ruleRegisterOrConstant)
targetReg = d.contents(argNodes[1])
wrappers = append(wrappers, finalTransform(d.output, instructionName, targetReg))
if isValidLEATarget(targetReg) {
return nil, errors.New("Currently transforming moves are assumed to target XMM registers. Otherwise we'll pop %rax before reading it to do the transform.")
}
case instrCombine:
targetReg = d.contents(argNodes[1])
if !isValidLEATarget(targetReg) {
return nil, fmt.Errorf("cannot handle combining instructions targeting non-general registers")
}
saveRegWrapper, tempReg := saveRegister(d.output, []string{targetReg})
redzoneCleared = true
wrappers = append(wrappers, saveRegWrapper)
wrappers = append(wrappers, combineOp(d.output, instructionName, tempReg, targetReg))
targetReg = tempReg
case instrMemoryVectorCombine:
assertNodeType(argNodes[1], ruleRegisterOrConstant)
targetReg = d.contents(argNodes[1])
if isValidLEATarget(targetReg) {
return nil, errors.New("target register must be an XMM register")
}
saveRegWrapper, tempReg := saveRegister(d.output, nil)
wrappers = append(wrappers, saveRegWrapper)
redzoneCleared = true
wrappers = append(wrappers, memoryVectorCombineOp(d.output, instructionName, tempReg, targetReg))
targetReg = tempReg
case instrThreeArg:
if n := len(argNodes); n != 3 {
return nil, fmt.Errorf("three-argument instruction has %d arguments", n)
}
if i != 0 && i != 1 {
return nil, errors.New("GOT access must be from source operand")
}
targetReg = d.contents(argNodes[2])
otherSource := d.contents(argNodes[1])
if i == 1 {
otherSource = d.contents(argNodes[0])
}
saveRegWrapper, tempReg := saveRegister(d.output, []string{targetReg, otherSource})
redzoneCleared = true
wrappers = append(wrappers, saveRegWrapper)
if i == 0 {
wrappers = append(wrappers, threeArgCombineOp(d.output, instructionName, tempReg, otherSource, targetReg))
} else {
wrappers = append(wrappers, threeArgCombineOp(d.output, instructionName, otherSource, tempReg, targetReg))
}
targetReg = tempReg
default:
return nil, fmt.Errorf("Cannot rewrite GOTPCREL reference for instruction %q", instructionName)
}
if !isValidLEATarget(targetReg) {
// Sometimes the compiler will load from the GOT to an
// XMM register, which is not a valid target of an LEA
// instruction.
saveRegWrapper, tempReg := saveRegister(d.output, nil)
wrappers = append(wrappers, saveRegWrapper)
isAVX := strings.HasPrefix(instructionName, "v")
wrappers = append(wrappers, moveTo(d.output, targetReg, isAVX, tempReg))
targetReg = tempReg
if redzoneCleared {
return nil, fmt.Errorf("internal error: Red Zone was already cleared")
}
redzoneCleared = true
}
if symbol == "OPENSSL_ia32cap_P" {
// Flag-altering instructions (i.e. addq) are going to be used so the
// flags need to be preserved.
wrappers = append(wrappers, saveFlags(d.output, redzoneCleared))
wrappers = append(wrappers, func(k func()) {
d.output.WriteString("\tleaq\tOPENSSL_ia32cap_addr_delta(%rip), " + targetReg + "\n")
d.output.WriteString("\taddq\t(" + targetReg + "), " + targetReg + "\n")
})
} else if useGOT {
wrappers = append(wrappers, d.loadFromGOT(d.output, targetReg, symbol, section, redzoneCleared))
} else {
wrappers = append(wrappers, func(k func()) {
d.output.WriteString(fmt.Sprintf("\tleaq\t%s(%%rip), %s\n", symbol, targetReg))
})
}
changed = true
break Args
default:
return nil, fmt.Errorf("Unknown section type %q", section)
}
if !changed && len(section) > 0 {
panic("section was not handled")
}
section = ""
argStr := ""
if isIndirect {
argStr += "*"
}
argStr += symbol
argStr += offset
for ; memRef != nil; memRef = memRef.next {
argStr += d.contents(memRef)
}
args = append(args, argStr)
case ruleGOTLocation:
if instructionName != "movabsq" {
return nil, fmt.Errorf("_GLOBAL_OFFSET_TABLE_ lookup didn't use movabsq")
}
if i != 0 || len(argNodes) != 2 {
return nil, fmt.Errorf("movabs of _GLOBAL_OFFSET_TABLE_ didn't expected form")
}
d.gotDeltaNeeded = true
changed = true
instructionName = "movq"
assertNodeType(arg.up, ruleLocalSymbol)
baseSymbol := d.mapLocalSymbol(d.contents(arg.up))
targetReg := d.contents(argNodes[1])
args = append(args, ".Lboringssl_got_delta(%rip)")
wrappers = append(wrappers, func(k func()) {
k()
d.output.WriteString(fmt.Sprintf("\taddq $.Lboringssl_got_delta-%s, %s\n", baseSymbol, targetReg))
})
case ruleGOTSymbolOffset:
if instructionName != "movabsq" {
return nil, fmt.Errorf("_GLOBAL_OFFSET_TABLE_ offset didn't use movabsq")
}
if i != 0 || len(argNodes) != 2 {
return nil, fmt.Errorf("movabs of _GLOBAL_OFFSET_TABLE_ offset didn't have expected form")
}
assertNodeType(arg.up, ruleSymbolName)
symbol := d.contents(arg.up)
if strings.HasPrefix(symbol, ".L") {
symbol = d.mapLocalSymbol(symbol)
}
targetReg := d.contents(argNodes[1])
var prefix string
isGOTOFF := strings.HasSuffix(d.contents(arg), "@GOTOFF")
if isGOTOFF {
prefix = "gotoff"
d.gotOffOffsetsNeeded[symbol] = struct{}{}
} else {
prefix = "got"
d.gotOffsetsNeeded[symbol] = struct{}{}
}
changed = true
wrappers = append(wrappers, func(k func()) {
// Even if one tries to use 32-bit GOT offsets, Clang's linker (at the time
// of writing) emits 64-bit relocations anyway, so the following four bytes
// get stomped. Thus we use 64-bit offsets.
d.output.WriteString(fmt.Sprintf("\tmovq .Lboringssl_%s_%s(%%rip), %s\n", prefix, symbol, targetReg))
})
default:
panic(fmt.Sprintf("unknown instruction argument type %q", rul3s[arg.pegRule]))
}
}
if changed {
d.writeCommentedNode(statement)
replacement := "\t" + instructionName + "\t" + strings.Join(args, ", ") + "\n"
wrappers.do(func() {
d.output.WriteString(replacement)
})
} else {
d.writeNode(statement)
}
return statement, nil
}