Skip to content

Commit 540b413

Browse files
committed
Add handling of structure returns and parameters in Windows x86/x64 calling conventions
1 parent 2e1edeb commit 540b413

3 files changed

Lines changed: 192 additions & 44 deletions

File tree

arch/x86/arch_x86.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3827,6 +3827,23 @@ class X86BaseCallingConvention: public CallingConvention
38273827
}
38283828
return result;
38293829
}
3830+
3831+
bool IsReturnTypeRegisterCompatible(Type* type) override
3832+
{
3833+
if (!type)
3834+
return false;
3835+
if (type->IsFloat())
3836+
return true;
3837+
if (type->GetWidth() == 0 || type->GetWidth() == 1 || type->GetWidth() == 2 || type->GetWidth() == 4
3838+
|| type->GetWidth() == 8)
3839+
return true;
3840+
return false;
3841+
}
3842+
3843+
std::optional<Variable> GetReturnedIndirectReturnValuePointer() override
3844+
{
3845+
return Variable::Register(XED_REG_EAX);
3846+
}
38303847
};
38313848

38323849

@@ -4060,6 +4077,36 @@ class X64WindowsCallingConvention: public X64BaseCallingConvention
40604077
{
40614078
return true;
40624079
}
4080+
4081+
bool IsReturnTypeRegisterCompatible(Type* type) override
4082+
{
4083+
if (!type)
4084+
return false;
4085+
if (type->IsFloat())
4086+
return true;
4087+
return type->GetWidth() == 0 || type->GetWidth() == 1 || type->GetWidth() == 2 || type->GetWidth() == 4
4088+
|| type->GetWidth() == 8;
4089+
}
4090+
4091+
std::optional<Variable> GetReturnedIndirectReturnValuePointer() override
4092+
{
4093+
return Variable::Register(XED_REG_RAX);
4094+
}
4095+
4096+
bool IsArgumentTypeRegisterCompatible(Type* type) override
4097+
{
4098+
if (!type)
4099+
return false;
4100+
if (type->IsFloat())
4101+
return true;
4102+
return type->GetWidth() == 0 || type->GetWidth() == 1 || type->GetWidth() == 2 || type->GetWidth() == 4
4103+
|| type->GetWidth() == 8;
4104+
}
4105+
4106+
bool AreNonRegisterArgumentsIndirect() override
4107+
{
4108+
return true;
4109+
}
40634110
};
40644111

40654112

plugins/pdb-ng/src/lib.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -898,6 +898,18 @@ fn init_plugin() -> bool {
898898
}"#,
899899
);
900900

901+
settings.register_setting_json(
902+
"pdb.features.passStructuresByValue",
903+
r#"{
904+
"title" : "Always Pass Structures By Value",
905+
"type" : "boolean",
906+
"default" : false,
907+
"aliases" : [],
908+
"description" : "Always pass structures by value even if they are implicitly passed by pointer in the calling convention (experimental). This more closely matches the original source code.",
909+
"ignore" : []
910+
}"#,
911+
);
912+
901913
true
902914
}
903915

plugins/pdb-ng/src/type_parser.rs

Lines changed: 133 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,17 @@ use crate::PDBParserInstance;
2020
use anyhow::{anyhow, Result};
2121
use binaryninja::architecture::Architecture;
2222
use binaryninja::binary_view::BinaryViewExt;
23-
use binaryninja::calling_convention::CoreCallingConvention;
23+
use binaryninja::calling_convention::{CallingConvention, CoreCallingConvention};
2424
use binaryninja::confidence::{Conf, MAX_CONFIDENCE};
2525
use binaryninja::platform::Platform;
2626
use binaryninja::rc::Ref;
2727
use binaryninja::types::{
2828
BaseStructure, EnumerationBuilder, EnumerationMember, FunctionParameter, MemberAccess,
29-
MemberScope, NamedTypeReference, NamedTypeReferenceClass, StructureBuilder, StructureMember,
30-
StructureType, Type, TypeBuilder, TypeClass,
29+
MemberScope, NamedTypeReference, NamedTypeReferenceClass, ReturnValue, StructureBuilder,
30+
StructureMember, StructureType, Type, TypeBuilder, TypeClass, ValueLocation,
31+
ValueLocationComponent,
3132
};
33+
use binaryninja::variable::{Variable, VariableSourceType};
3234
use pdb::Error::UnimplementedTypeKind;
3335
use pdb::{
3436
ArgumentList, ArrayType, BaseClassType, BitfieldType, ClassKind, ClassType, EnumerateType,
@@ -1143,30 +1145,12 @@ impl<'a, S: Source<'a> + 'a> PDBParserInstance<'a, S> {
11431145
}
11441146
}
11451147

1146-
let mut fancy_return_type = return_type.clone();
1148+
let mut fancy_return_value = ReturnValue {
1149+
ty: Conf::new(return_type.clone(), MAX_CONFIDENCE),
1150+
location: None,
1151+
};
11471152
let mut fancy_arguments = arguments.clone();
11481153

1149-
if data.attributes.cxx_return_udt()
1150-
|| !self.can_fit_in_register(data.return_type, finder, true)
1151-
{
1152-
// Return UDT??
1153-
// This probably means the return value got pushed to the stack
1154-
fancy_return_type =
1155-
Type::pointer(&self.arch, &Conf::new(return_type.clone(), MAX_CONFIDENCE));
1156-
fancy_arguments.insert(
1157-
0,
1158-
FunctionParameter::new(
1159-
Conf::new(fancy_return_type.clone(), MAX_CONFIDENCE),
1160-
"__return".to_string(),
1161-
None,
1162-
),
1163-
);
1164-
}
1165-
1166-
if let Some(this_ptr) = &this_pointer_type {
1167-
self.insert_this_pointer(&mut fancy_arguments, this_ptr.clone())?;
1168-
}
1169-
11701154
let convention = self
11711155
.cv_call_t_to_calling_convention(data.attributes.calling_convention())
11721156
.map(|cc| Conf::new(cc, MAX_CONFIDENCE))
@@ -1180,6 +1164,37 @@ impl<'a, S: Source<'a> + 'a> PDBParserInstance<'a, S> {
11801164
}
11811165
});
11821166

1167+
if data.attributes.cxx_return_udt()
1168+
|| !self.can_fit_in_register(data.return_type, finder, true)
1169+
{
1170+
// Return UDT??
1171+
// This probably means the return value got pushed to the stack
1172+
if self.settings.get_bool_with_opts(
1173+
"pdb.features.passStructuresByValue",
1174+
&mut self.settings_query_opts,
1175+
) {
1176+
fancy_return_value.location =
1177+
self.indirect_return_value_location(&convention, &fancy_return_value);
1178+
} else {
1179+
fancy_return_value.ty = Conf::new(
1180+
Type::pointer(&self.arch, &return_type.clone()),
1181+
MAX_CONFIDENCE,
1182+
);
1183+
fancy_arguments.insert(
1184+
0,
1185+
FunctionParameter::new(
1186+
fancy_return_value.ty.clone(),
1187+
"__return".to_string(),
1188+
None,
1189+
),
1190+
);
1191+
}
1192+
}
1193+
1194+
if let Some(this_ptr) = &this_pointer_type {
1195+
self.insert_this_pointer(&mut fancy_arguments, this_ptr.clone())?;
1196+
}
1197+
11831198
let func = Type::function_with_opts(
11841199
&Conf::new(return_type, MAX_CONFIDENCE),
11851200
arguments.as_slice(),
@@ -1189,7 +1204,7 @@ impl<'a, S: Source<'a> + 'a> PDBParserInstance<'a, S> {
11891204
);
11901205

11911206
let fancy_func = Type::function_with_opts(
1192-
&Conf::new(fancy_return_type, MAX_CONFIDENCE),
1207+
fancy_return_value,
11931208
fancy_arguments.as_slice(),
11941209
is_varargs,
11951210
convention,
@@ -1423,31 +1438,46 @@ impl<'a, S: Source<'a> + 'a> PDBParserInstance<'a, S> {
14231438
}
14241439
}
14251440

1426-
let mut fancy_return_type = return_type.clone();
1441+
let mut fancy_return_value = ReturnValue {
1442+
ty: return_type.clone(),
1443+
location: None,
1444+
};
14271445
let mut fancy_arguments = arguments.clone();
14281446

1447+
let convention = self
1448+
.cv_call_t_to_calling_convention(data.attributes.calling_convention())
1449+
.map(|cc| Conf::new(cc, MAX_CONFIDENCE))
1450+
.unwrap_or(Conf::new(self.default_cc.clone(), 0));
1451+
self.log(|| format!("Convention: {:?}", convention));
1452+
14291453
let mut return_stacky = data.attributes.cxx_return_udt();
14301454
if let Some(return_type_index) = data.return_type {
14311455
return_stacky |= !self.can_fit_in_register(return_type_index, finder, true);
14321456
}
14331457
if return_stacky {
14341458
// Stack return via a pointer in the first parameter
1435-
fancy_return_type = Conf::new(
1436-
Type::pointer(&self.arch, &return_type.clone()),
1437-
MAX_CONFIDENCE,
1438-
);
1439-
fancy_arguments.insert(
1440-
0,
1441-
FunctionParameter::new(fancy_return_type.clone(), "__return".to_string(), None),
1442-
);
1459+
if self.settings.get_bool_with_opts(
1460+
"pdb.features.passStructuresByValue",
1461+
&mut self.settings_query_opts,
1462+
) {
1463+
fancy_return_value.location =
1464+
self.indirect_return_value_location(&convention, &fancy_return_value);
1465+
} else {
1466+
fancy_return_value.ty = Conf::new(
1467+
Type::pointer(&self.arch, &return_type.clone()),
1468+
MAX_CONFIDENCE,
1469+
);
1470+
fancy_arguments.insert(
1471+
0,
1472+
FunctionParameter::new(
1473+
fancy_return_value.ty.clone(),
1474+
"__return".to_string(),
1475+
None,
1476+
),
1477+
);
1478+
}
14431479
}
14441480

1445-
let convention = self
1446-
.cv_call_t_to_calling_convention(data.attributes.calling_convention())
1447-
.map(|cc| Conf::new(cc, MAX_CONFIDENCE))
1448-
.unwrap_or(Conf::new(self.default_cc.clone(), 0));
1449-
self.log(|| format!("Convention: {:?}", convention));
1450-
14511481
let func = Type::function_with_opts(
14521482
&return_type,
14531483
arguments.as_slice(),
@@ -1457,7 +1487,7 @@ impl<'a, S: Source<'a> + 'a> PDBParserInstance<'a, S> {
14571487
);
14581488

14591489
let fancy_func = Type::function_with_opts(
1460-
&fancy_return_type,
1490+
fancy_return_value,
14611491
fancy_arguments.as_slice(),
14621492
is_varargs,
14631493
convention,
@@ -1815,8 +1845,13 @@ impl<'a, S: Source<'a> + 'a> PDBParserInstance<'a, S> {
18151845
Some(ty) => {
18161846
// On x86_32, structures are stored on the stack directly
18171847
// On x64, they are put into pointers if they are not a int size
1818-
// TODO: Ugly hack
1819-
if self.arch.address_size() == 4 || Self::size_can_fit_in_register(ty.width()) {
1848+
if self.arch.address_size() == 4
1849+
|| Self::size_can_fit_in_register(ty.width())
1850+
|| self.settings.get_bool_with_opts(
1851+
"pdb.features.passStructuresByValue",
1852+
&mut self.settings_query_opts,
1853+
)
1854+
{
18201855
args.push(FunctionParameter::new(
18211856
Conf::new(ty.clone(), MAX_CONFIDENCE),
18221857
"".to_string(),
@@ -2372,4 +2407,58 @@ impl<'a, S: Source<'a> + 'a> PDBParserInstance<'a, S> {
23722407
_ => false,
23732408
}
23742409
}
2410+
2411+
/// Determines if there is a non-default location for an indirect return value and returns
2412+
/// the location if there is one.
2413+
fn indirect_return_value_location(
2414+
&self,
2415+
convention: &Conf<Ref<CoreCallingConvention>>,
2416+
return_value: &ReturnValue,
2417+
) -> Option<Conf<ValueLocation>> {
2418+
// Non-POD data types are always returned as indirect values. The calling convention
2419+
// may not know this and try to place them in registers, so check the calling convention
2420+
// to see if it wants to pass indirectly.
2421+
// TODO: The structures themselves should have some kind of non-POD attribute so
2422+
// that the calling convention can determine this by default
2423+
let default_return_location = convention
2424+
.contents
2425+
.return_value_location(return_value.clone());
2426+
let default_return_indrect = default_return_location
2427+
.components
2428+
.iter()
2429+
.any(|c| c.indirect);
2430+
if default_return_indrect {
2431+
None
2432+
} else {
2433+
let variable = if let Some(reg) = convention.contents.int_arg_registers().get(0) {
2434+
Variable::new(
2435+
VariableSourceType::RegisterVariableSourceType,
2436+
0,
2437+
reg.0 as i64,
2438+
)
2439+
} else {
2440+
Variable::new(
2441+
VariableSourceType::StackVariableSourceType,
2442+
0,
2443+
self.arch.address_size() as i64,
2444+
)
2445+
};
2446+
Some(Conf::new(
2447+
ValueLocation {
2448+
components: vec![ValueLocationComponent {
2449+
variable,
2450+
offset: 0,
2451+
size: Some(return_value.ty.contents.width()),
2452+
indirect: true,
2453+
returned_pointer: convention.contents.return_int_reg().map(|reg| Variable::new(
2454+
VariableSourceType::RegisterVariableSourceType,
2455+
0,
2456+
reg.0 as i64,
2457+
)),
2458+
}],
2459+
},
2460+
MAX_CONFIDENCE,
2461+
))
2462+
}
2463+
}
23752464
}

0 commit comments

Comments
 (0)