// Function stack size counter
// Copyright (C) 2025 Nguyễn Gia Phong
//
// This file is part of taosc.
//
// Taosc is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Taosc is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with taosc. If not, see .
#include "helpers.hh"
// Dyninst headers
#include
#include
#include
#include
#include
using Architecture = Dyninst::Architecture;
using Address = Dyninst::Address;
using Function = Dyninst::ParseAPI::Function;
using CodeObject = Dyninst::ParseAPI::CodeObject;
using CodeSource = Dyninst::ParseAPI::SymtabCodeSource;
using Instruction = Dyninst::InstructionAPI::Instruction;
using InstructionDecoder = Dyninst::InstructionAPI::InstructionDecoder;
using MachRegister = Dyninst::MachRegister;
using Operand = Dyninst::InstructionAPI::Operand;
using RegisterAST = Dyninst::InstructionAPI::RegisterAST;
#include
#include
#include
#include
#include
static int64_t
immediate (Operand const& operand)
{
auto const& result = operand.getValue ().get ()->eval ();
assert (result.defined);
switch (result.type)
{
case Dyninst::InstructionAPI::s8:
case Dyninst::InstructionAPI::u8:
return result.val.s8val;
case Dyninst::InstructionAPI::s16:
case Dyninst::InstructionAPI::u16:
return result.val.s16val;
case Dyninst::InstructionAPI::s32:
case Dyninst::InstructionAPI::u32:
return result.val.s32val;
case Dyninst::InstructionAPI::s64:
case Dyninst::InstructionAPI::u64:
return result.val.s64val;
default:
std::unreachable ();
}
}
static int64_t
stack_offset (Instruction const& instruction, Architecture architecture)
{
switch (instruction.getOperation ().getID ())
{
case e_push:
return Dyninst::getArchAddressWidth (architecture);
case e_pop:
return -Dyninst::getArchAddressWidth (architecture);
case e_sub:
return immediate (instruction.getOperand (1));
case e_add:
return -immediate (instruction.getOperand (1));
default:
std::unreachable ();
}
}
static size_t
stack_offset (char const* buffer, size_t length,
RegisterAST::Ptr stack_pointer,
Architecture architecture)
{
InstructionDecoder decoder {buffer, length, architecture};
size_t offset = 0;
for (auto instruction = decoder.decode ();
instruction.isValid ();
instruction = decoder.decode ())
if (instruction.isWritten (stack_pointer))
offset += stack_offset (instruction, architecture);
return offset;
}
int
main (int argc, char** argv)
{
CodeSource cs {parse_args (argc, argv)};
auto const architecture = cs.getArch ();
RegisterAST::Ptr const stack_pointer
{new RegisterAST(MachRegister::getStackPointer (architecture))};
CodeObject co {&cs};
co.parse (); // parsed functions have same lifetime as co
while (!std::cin.eof ())
{
Address address;
std::cin >> std::hex >> address;
if (std::cin.fail ())
break;
auto* const block = find_block (cs, co, address);
if (block->containingFuncs () < 1)
die_for (address, "no function found containing instruction at");
std::vector functions;
block->getFuncs (functions);
size_t stack_size = 0;
for (auto* const fun : functions)
{
size_t offset = 0;
for (auto* prologue = fun->entry ();
prologue;
prologue = (prologue->targets ().size () == 1
? (*prologue->targets ().begin ())->trg ()
: nullptr))
{
auto const* const buffer = (char*)
cs.getPtrToInstruction (prologue->start ());
offset += stack_offset (buffer, prologue->size (),
stack_pointer, architecture);
}
if (offset == 0)
continue;
if (stack_size == 0)
stack_size = offset;
else if (offset != stack_size)
die_for (address, "functions with different stack sizes spanning");
}
std::cout << stack_size << '\n';
}
if (std::cin.eof ())
return 0;
std::cerr << "invalid input\n";
return -1;
}