Skip to main content

StackSlot

Struct StackSlot 

Source
pub struct StackSlot { /* private fields */ }
Expand description

One slot in a function’s stack frame, its frame-pointer-relative offset and byte size, plus a kind that is either a real variable (with name/type) or a reserved slot.

Implementations§

Source§

impl StackSlot

Source

pub const fn offset(&self) -> i64

The frame-pointer-relative offset IDA displays: negative below the frame pointer (locals), positive above it (the return address, then stack arguments).

Examples found in repository?
examples/types.rs (line 146)
130fn print_frame(frame: &StackFrame, ea: Address) {
131    println!(
132        "\n== stack frame: {ea:#x}  ({} bytes, {} slots) ==",
133        frame.size(),
134        frame.len()
135    );
136    for v in frame.slots() {
137        let ty = v
138            .ty()
139            .map_or_else(|| "-".to_owned(), |id| one_line(frame.types(), id));
140        let label = match v.kind() {
141            StackSlotKind::Variable { name, .. } if !name.is_empty() => name.clone(),
142            StackSlotKind::Variable { .. } => "<unnamed>".to_owned(),
143            StackSlotKind::ReturnAddress => "<return address>".to_owned(),
144            StackSlotKind::SavedRegisters => "<saved registers>".to_owned(),
145        };
146        println!("  {:>8}  {label}", soff(v.offset()));
147        if ty != "-" {
148            println!("            {ty}");
149        }
150    }
151}
152
153fn main() -> Result<(), Box<dyn std::error::Error>> {
154    let mut argv = std::env::args().skip(1);
155    let db = argv.next().expect("usage: types <db.i64> [TypeName]");
156    let arg_type = argv.next();
157
158    Ida::run(move |ida| -> Result<(), Error> {
159        ida.call(move |idb| -> Result<(), Error> {
160            idb.open(&db).call()?;
161
162            // Prototypes are sparse in a stripped release binary, so scan every function, not a
163            // prefix, so the reported ratio is honest and the sample isn't just entry-point stubs.
164            let mut total = 0usize;
165            let mut typed = 0usize;
166            let mut shown: Vec<(Address, String, Type)> = Vec::new();
167            let mut names: Vec<String> = Vec::new();
168            let mut best_frame: Option<(Address, StackFrame)> = None;
169            let mut best_vars = 0usize;
170            let mut frames_tried = 0usize;
171
172            for f in idb.functions() {
173                total += 1;
174
175                if let Some(image) = f.prototype_type()? {
176                    typed += 1;
177                    for (_, t) in image.types().iter() {
178                        if let Some(n) = referenced_name(&t.shape)
179                            && names.len() < MAX_NAMES
180                            && !names.iter().any(|x| x == n)
181                        {
182                            names.push(n.to_owned());
183                        }
184                    }
185                    if shown.len() < SHOW_PROTOS {
186                        shown.push((f.address(), f.name().as_str().to_owned(), image));
187                    }
188                }
189
190                if frames_tried < FRAME_BUDGET
191                    && let Some(frame) = idb.frame(f.address())?
192                {
193                    frames_tried += 1;
194                    // A local lives within its frame; an offset in the millions is IDA's own
195                    // misanalysis of a garbage function. Skip such frames so we showcase a real one.
196                    let locals = frame.slots().iter().filter(|v| !v.is_special());
197                    let sane = locals.clone().all(|v| v.offset().unsigned_abs() < 0x1_0000);
198                    let n = locals.count();
199                    if sane && (best_frame.is_none() || n > best_vars) {
200                        best_vars = n;
201                        best_frame = Some((f.address(), frame));
202                    }
203                }
204            }
205
206            println!("== function prototypes ==");
207            println!("{typed} of {total} functions carry a stored prototype.\n");
208            for (ea, sym, image) in &shown {
209                println!("  {ea:#x}  {}", one_line(image.types(), image.root()));
210                let short: String = sym.chars().take(64).collect();
211                if !short.is_empty() {
212                    println!("               {short}");
213                }
214            }
215            if let Some((ea, _, image)) = shown.iter().max_by_key(|(_, _, im)| param_count(im))
216                && let TypeShape::Function {
217                    ret,
218                    params,
219                    varargs,
220                } = image.shape()
221                && !params.is_empty()
222            {
223                println!("\n  every parameter is a resolved TypeId -- {ea:#x}:");
224                println!("    ret    {}", one_line(image.types(), *ret));
225                for (i, p) in params.iter().enumerate() {
226                    println!("    arg{i}   {}", one_line(image.types(), *p));
227                }
228                if *varargs {
229                    println!("    ...");
230                }
231            }
232
233            // The named-type pass: resolve every name the prototypes reference and classify what
234            // the database actually holds, a full body to expand, or just a forward declaration.
235            println!("\n== referenced named types ==");
236            if let Some(name) = &arg_type {
237                match idb.type_named(name) {
238                    Ok(image) => print_layout(&image, name),
239                    Err(e) => println!("  type_named({name:?}): {e}"),
240                }
241            } else {
242                let mut bodies: Vec<(String, Type)> = Vec::new();
243                let mut forward: Vec<String> = Vec::new();
244                let mut not_local = 0usize;
245                for name in &names {
246                    match idb.type_named(name) {
247                        Ok(image) if image.members().is_some_and(|m| !m.is_empty()) => {
248                            bodies.push((name.clone(), image));
249                        }
250                        Ok(_) => forward.push(name.clone()),
251                        Err(Error::TypeNotFound { .. }) => not_local += 1,
252                        Err(e) => println!("  type_named({name:?}): {e}"),
253                    }
254                }
255                println!(
256                    "{} referenced: {} with a full body, {} forward-declared, {} not a local type.",
257                    names.len(),
258                    bodies.len(),
259                    forward.len(),
260                    not_local
261                );
262                if let Some((name, image)) = bodies
263                    .iter()
264                    .max_by_key(|(_, im)| im.members().map_or(0, <[_]>::len))
265                {
266                    print_layout(image, name);
267                } else {
268                    for n in forward.iter().take(6) {
269                        println!("  forward-decl: {n}");
270                    }
271                }
272            }
273
274            match &best_frame {
275                Some((ea, frame)) => print_frame(frame, *ea),
276                None => println!("\n(no function has a stack frame)"),
277            }
278
279            idb.close(false);
280            println!("\nTYPES OK");
281            Ok(())
282        })?
283    })??;
284
285    Ok(())
286}
Source

pub const fn size(&self) -> u64

The slot’s size in bytes.

Source

pub const fn kind(&self) -> &StackSlotKind

What this slot is: a real variable (with name/type) or a reserved slot.

Examples found in repository?
examples/types.rs (line 140)
130fn print_frame(frame: &StackFrame, ea: Address) {
131    println!(
132        "\n== stack frame: {ea:#x}  ({} bytes, {} slots) ==",
133        frame.size(),
134        frame.len()
135    );
136    for v in frame.slots() {
137        let ty = v
138            .ty()
139            .map_or_else(|| "-".to_owned(), |id| one_line(frame.types(), id));
140        let label = match v.kind() {
141            StackSlotKind::Variable { name, .. } if !name.is_empty() => name.clone(),
142            StackSlotKind::Variable { .. } => "<unnamed>".to_owned(),
143            StackSlotKind::ReturnAddress => "<return address>".to_owned(),
144            StackSlotKind::SavedRegisters => "<saved registers>".to_owned(),
145        };
146        println!("  {:>8}  {label}", soff(v.offset()));
147        if ty != "-" {
148            println!("            {ty}");
149        }
150    }
151}
Source

pub fn name(&self) -> Option<&str>

The variable’s name, or None for a reserved slot.

Shortcut into kind.

Source

pub fn ty(&self) -> Option<TypeId>

The variable’s structured type handle, or None for a reserved slot or an untyped stack slot.

Resolve it against the owning StackFrame with StackFrame::type_of. Shortcut into kind.

Examples found in repository?
examples/types.rs (line 138)
130fn print_frame(frame: &StackFrame, ea: Address) {
131    println!(
132        "\n== stack frame: {ea:#x}  ({} bytes, {} slots) ==",
133        frame.size(),
134        frame.len()
135    );
136    for v in frame.slots() {
137        let ty = v
138            .ty()
139            .map_or_else(|| "-".to_owned(), |id| one_line(frame.types(), id));
140        let label = match v.kind() {
141            StackSlotKind::Variable { name, .. } if !name.is_empty() => name.clone(),
142            StackSlotKind::Variable { .. } => "<unnamed>".to_owned(),
143            StackSlotKind::ReturnAddress => "<return address>".to_owned(),
144            StackSlotKind::SavedRegisters => "<saved registers>".to_owned(),
145        };
146        println!("  {:>8}  {label}", soff(v.offset()));
147        if ty != "-" {
148            println!("            {ty}");
149        }
150    }
151}
Source

pub const fn is_special(&self) -> bool

Whether this is one of IDA’s reserved slots (return address or saved registers) rather than a real variable.

Examples found in repository?
examples/types.rs (line 196)
153fn main() -> Result<(), Box<dyn std::error::Error>> {
154    let mut argv = std::env::args().skip(1);
155    let db = argv.next().expect("usage: types <db.i64> [TypeName]");
156    let arg_type = argv.next();
157
158    Ida::run(move |ida| -> Result<(), Error> {
159        ida.call(move |idb| -> Result<(), Error> {
160            idb.open(&db).call()?;
161
162            // Prototypes are sparse in a stripped release binary, so scan every function, not a
163            // prefix, so the reported ratio is honest and the sample isn't just entry-point stubs.
164            let mut total = 0usize;
165            let mut typed = 0usize;
166            let mut shown: Vec<(Address, String, Type)> = Vec::new();
167            let mut names: Vec<String> = Vec::new();
168            let mut best_frame: Option<(Address, StackFrame)> = None;
169            let mut best_vars = 0usize;
170            let mut frames_tried = 0usize;
171
172            for f in idb.functions() {
173                total += 1;
174
175                if let Some(image) = f.prototype_type()? {
176                    typed += 1;
177                    for (_, t) in image.types().iter() {
178                        if let Some(n) = referenced_name(&t.shape)
179                            && names.len() < MAX_NAMES
180                            && !names.iter().any(|x| x == n)
181                        {
182                            names.push(n.to_owned());
183                        }
184                    }
185                    if shown.len() < SHOW_PROTOS {
186                        shown.push((f.address(), f.name().as_str().to_owned(), image));
187                    }
188                }
189
190                if frames_tried < FRAME_BUDGET
191                    && let Some(frame) = idb.frame(f.address())?
192                {
193                    frames_tried += 1;
194                    // A local lives within its frame; an offset in the millions is IDA's own
195                    // misanalysis of a garbage function. Skip such frames so we showcase a real one.
196                    let locals = frame.slots().iter().filter(|v| !v.is_special());
197                    let sane = locals.clone().all(|v| v.offset().unsigned_abs() < 0x1_0000);
198                    let n = locals.count();
199                    if sane && (best_frame.is_none() || n > best_vars) {
200                        best_vars = n;
201                        best_frame = Some((f.address(), frame));
202                    }
203                }
204            }
205
206            println!("== function prototypes ==");
207            println!("{typed} of {total} functions carry a stored prototype.\n");
208            for (ea, sym, image) in &shown {
209                println!("  {ea:#x}  {}", one_line(image.types(), image.root()));
210                let short: String = sym.chars().take(64).collect();
211                if !short.is_empty() {
212                    println!("               {short}");
213                }
214            }
215            if let Some((ea, _, image)) = shown.iter().max_by_key(|(_, _, im)| param_count(im))
216                && let TypeShape::Function {
217                    ret,
218                    params,
219                    varargs,
220                } = image.shape()
221                && !params.is_empty()
222            {
223                println!("\n  every parameter is a resolved TypeId -- {ea:#x}:");
224                println!("    ret    {}", one_line(image.types(), *ret));
225                for (i, p) in params.iter().enumerate() {
226                    println!("    arg{i}   {}", one_line(image.types(), *p));
227                }
228                if *varargs {
229                    println!("    ...");
230                }
231            }
232
233            // The named-type pass: resolve every name the prototypes reference and classify what
234            // the database actually holds, a full body to expand, or just a forward declaration.
235            println!("\n== referenced named types ==");
236            if let Some(name) = &arg_type {
237                match idb.type_named(name) {
238                    Ok(image) => print_layout(&image, name),
239                    Err(e) => println!("  type_named({name:?}): {e}"),
240                }
241            } else {
242                let mut bodies: Vec<(String, Type)> = Vec::new();
243                let mut forward: Vec<String> = Vec::new();
244                let mut not_local = 0usize;
245                for name in &names {
246                    match idb.type_named(name) {
247                        Ok(image) if image.members().is_some_and(|m| !m.is_empty()) => {
248                            bodies.push((name.clone(), image));
249                        }
250                        Ok(_) => forward.push(name.clone()),
251                        Err(Error::TypeNotFound { .. }) => not_local += 1,
252                        Err(e) => println!("  type_named({name:?}): {e}"),
253                    }
254                }
255                println!(
256                    "{} referenced: {} with a full body, {} forward-declared, {} not a local type.",
257                    names.len(),
258                    bodies.len(),
259                    forward.len(),
260                    not_local
261                );
262                if let Some((name, image)) = bodies
263                    .iter()
264                    .max_by_key(|(_, im)| im.members().map_or(0, <[_]>::len))
265                {
266                    print_layout(image, name);
267                } else {
268                    for n in forward.iter().take(6) {
269                        println!("  forward-decl: {n}");
270                    }
271                }
272            }
273
274            match &best_frame {
275                Some((ea, frame)) => print_frame(frame, *ea),
276                None => println!("\n(no function has a stack frame)"),
277            }
278
279            idb.close(false);
280            println!("\nTYPES OK");
281            Ok(())
282        })?
283    })??;
284
285    Ok(())
286}

Trait Implementations§

Source§

impl Clone for StackSlot

Source§

fn clone(&self) -> StackSlot

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for StackSlot

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for StackSlot

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl Eq for StackSlot

Source§

impl Hash for StackSlot

Source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl PartialEq for StackSlot

Source§

fn eq(&self, other: &StackSlot) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl Serialize for StackSlot

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl StructuralPartialEq for StackSlot

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more