#!/usr/bin/env python3
# Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
# For licensing, see https://github.com/mudita/MuditaOS/blob/master/LICENSE.md
"""
detect static globals in code
@package globalFinder
"""
import argparse
import magic
import os
import re
import subprocess
import sys
from enum import Enum
from queue import Queue
from threading import Thread
class ArchType(Enum):
"""Define supported architecture types"""
X86 = 1
ARM = 2
class Finder():
"""Finds potential places where global can be.
This class holds all necessary variables and is responsible for holding and
presenting results.
Attributes
----------
elfPath : str
path to inspected ELF
arch : ArchType
detected architecture
objdump : str
used `objdump` command (depends on arch)
nm : str
used `nm` command (depends on arch)
sections : Queue
stores list of sections with "StaticSymbol"
resultsQueue : Queue
stores found items, all threads are puting results here one result one item
results : dict
gathers results from `resultsQueue` used for counting items and printing report
"""
armRe = re.compile("ELF 32-bit .*ARM.*")
x86Re = re.compile("ELF (64|32)-bit .*x86.*")
staticSymbol = "_Z41__static_initialization_and_destruction_0ii"
staticRe = re.compile("([0-9a-fA-F]+) ([0-9a-fA-F]+) .*" + staticSymbol + ".*")
def __init__(self, path):
"""
Parameters
----------
path : str
path to elf that will be inspected
"""
self.elfPath = path
self.arch = None
self.objdump = None
self.nm = None
self.sections = Queue()
self.resultsQueue = Queue()
self.results = {}
def checkArch(self):
"""Detect elf architecture"""
magicString = magic.from_file(self.elfPath)
if self.x86Re.match(magicString):
self.arch = ArchType.X86
self.objdump = "objdump"
self.nm = "nm"
elif self.armRe.match(magicString):
self.arch = ArchType.ARM
self.objdump = "arm-none-eabi-objdump"
self.nm = "arm-none-eabi-nm"
else:
self.arch = None
def findStatic(self):
"""find sections with static initialization symbol"""
print("searching for statics")
out = subprocess.run([self.nm, "--print-size", self.elfPath], capture_output=True, text=True)
lines = out.stdout.split('\n')
for line in lines:
match = self.staticRe.match(line)
if match:
startAddress = int(match.group(1), 16)
sectionSize = int(match.group(2), 16)
endAddress = startAddress + sectionSize
self.sections.put(("0x%x" % startAddress, "0x%x" % endAddress))
def countResults(self):
"""Count found items"""
while not self.resultsQueue.empty():
path, lineNo = self.resultsQueue.get()
realpath = os.path.realpath(path)
key = realpath + ":" + lineNo
if key not in self.results:
self.results[key] = 1
else:
self.results[key] += 1
class StaticChecker(Thread):
"""Checks if found sections contain static symbols.
This calass runs in separate processes objdump, deasembles section and search for hpp file.
if found such place is logged.
Attributes
----------
finder : Finder
access communication queues, and all other needed variables
hppRegexp : re
regexp for detecting hpp files
"""
hppRegexp = re.compile("(.*\.hpp):([0-9]+)")
def __init__(self, finder):
"""
Parameters
----------
finder : Finder
Finder object to work with
"""
Thread.__init__(self)
self.finder = finder
def run(self):
while True:
print("-thread start:", self.native_id, self.finder.sections.qsize())
if not self.finder.sections.empty():
startAddress, stopAddress = self.finder.sections.get()
try:
out = subprocess.run([self.finder.objdump,
"--start-address="+startAddress,
"--stop-address="+stopAddress,
"-d", "-l",
self.finder.elfPath
],
capture_output=True, text=True)
for line in out.stdout.split('\n'):
reMatch = self.hppRegexp.match(line)
if reMatch:
self.finder.resultsQueue.put((reMatch.group(1), reMatch.group(2)))
print(" ", reMatch.group(1), reMatch.group(2))
finally:
self.finder.sections.task_done()
else:
return
def main():
parser = argparse.ArgumentParser(description="Find globals in code")
parser.add_argument('elfPath', help="Path to elf file")
args = parser.parse_args()
finder = Finder(args.elfPath)
finder.checkArch()
finder.findStatic()
cpuCount = os.cpu_count()
if cpuCount is None:
cpuCount = 2
for _ in range(cpuCount):
staticChecker = StaticChecker(finder)
staticChecker.daemon = True
staticChecker.start()
finder.sections.join()
finder.countResults()
if len(finder.results) >= 1:
print(finder.results)
sys.exit(1)
if __name__ == "__main__":
main()