├── LICENSE ├── README.md ├── architectures ├── aarch64.py ├── arm.py ├── generic.py ├── intel.py ├── mips.py ├── ppc.py └── riscv.py ├── converter.py ├── exporter.py ├── mmushell.py ├── qemu ├── README ├── build_qemu ├── qemu_logger.py ├── qemu_v5.0.0.patch └── requirements.txt └── requirements.txt /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mmushell 2 | MMUShell OS-Agnostic Memory Forensics Tool 3 | 4 | Proof of concept for techniques developed by Andrea Oliveri and Davide Balzarotti in 5 | 6 | ["In the Land of MMUs: Multiarchitecture OS-Agnostic Virtual Memory Forensics"](https://doi.org/10.1145/3528102) 7 | 8 | Installation: 9 | ``` 10 | pip install -r requirements.txt 11 | ``` 12 | 13 | Usage: 14 | - Dump all the RAM areas of the machine that you want to analyze in raw format, one file per physical memory area. 15 | - Create a YAML file describing the hardware configuration of the machine (see the examples available in the dataset). 16 | - ```mmushell machine.yaml``` 17 | - Use the interactive shell to find MMU registers, Radix-Trees, Hash tables etc. and explore them. The ```help``` command lists all the possible actions available for the selected CPU architecture. 18 | - [Here](https://www.s3.eurecom.fr/datasets/datasets_old_www/mmushell_dataset.tar) part of the dataset containing the memory dumps of the OSs used in the paper (only the open-source ones, due to license restrictions). 19 | - ```/qemu/``` contains the patch for QEMU 5.0.0 in order to collect the ground truth values of the MMU registers during OSs execution. 20 | -------------------------------------------------------------------------------- /architectures/mips.py: -------------------------------------------------------------------------------- 1 | from architectures.generic import Machine as MachineDefault 2 | from architectures.generic import CPU as CPUDefault 3 | from architectures.generic import PhysicalMemory as PhysicalMemoryDefault 4 | from architectures.generic import MMUShell as MMUShellDefault 5 | from architectures.generic import MMU as MMUDefault 6 | from architectures.generic import CPUReg 7 | import logging 8 | from prettytable import PrettyTable 9 | from dataclasses import dataclass 10 | from tqdm import tqdm 11 | from struct import unpack 12 | from collections import defaultdict 13 | from miasm.analysis.machine import Machine as MIASMMachine 14 | from miasm.core.bin_stream import bin_stream_vm 15 | from miasm.core.locationdb import LocationDB 16 | from miasm.jitter.VmMngr import Vm 17 | from miasm.jitter.csts import PAGE_READ, PAGE_WRITE, PAGE_EXEC 18 | from copy import deepcopy 19 | from pprint import pprint 20 | 21 | logger = logging.getLogger(__name__) 22 | 23 | 24 | @dataclass 25 | class Data: 26 | is_mem_parsed: bool 27 | is_registers_found: bool 28 | opcodes: dict 29 | regs_values: dict 30 | 31 | 32 | class Machine(MachineDefault): 33 | def __init__(self, cpu, mmu, memory, **kwargs): 34 | super(Machine, self).__init__(cpu, mmu, memory, **kwargs) 35 | 36 | def get_miasm_machine(self): 37 | mn_s = "mips" + str(self.cpu.bits) + ("b" if self.cpu.endianness == "big" else "l") 38 | return MIASMMachine(mn_s) 39 | 40 | 41 | class CPURegMIPS(CPUReg): 42 | @classmethod 43 | def get_register_obj(cls, reg_name, value): 44 | return globals()[reg_name](value) 45 | 46 | 47 | class ContextConfig(CPURegMIPS): 48 | def is_valid(self, value): 49 | # Check if it is a mask of contigous 1s 50 | digits = bin(value)[2:] 51 | if len(digits) == 1: 52 | return True 53 | 54 | digit_changes = 0 55 | for i in range(1, len(digits)): 56 | if digits[i-1] != digits[i]: 57 | digit_changes += 1 58 | 59 | if digits[0] == "1" or digits[-1] == "1": 60 | return digit_changes <= 1 61 | else: 62 | return digit_changes <= 2 63 | 64 | def __init__(self, value): 65 | self.value = value 66 | if self.is_valid(value): 67 | self.valid = True 68 | self.VirtualIndex = value 69 | else: 70 | self.valid = False 71 | 72 | def is_mmu_equivalent_to(self, other): 73 | return self.valid == other.valid and self.VirtualIndex == other.VirtualIndex 74 | 75 | def __repr__(self): 76 | return f"ContextConfig {hex(self.value)} => VirtualIndex:{hex(self.VirtualIndex)}" 77 | 78 | 79 | class PageMask(CPURegMIPS): 80 | def is_valid(self, value): 81 | if CPU.extract_bits(value, 0, 11) != 0x0 or CPU.extract_bits(value, 29, 3) != 0x0: 82 | return False 83 | 84 | MaskX = CPU.extract_bits(value, 11, 2) 85 | Mask = CPU.extract_bits(value, 13, 16) 86 | return self._is_a_valid_mask(Mask) and self._is_a_valid_mask(MaskX) 87 | 88 | def _is_a_valid_mask(self, value): 89 | # Check if it is a mask of 1s 90 | digits = bin(value)[2:] 91 | if len(digits) == 1: 92 | return True 93 | 94 | digit_changes = 0 95 | for i in range(1, len(digits)): 96 | if digits[i-1] != digits[i]: 97 | digit_changes += 1 98 | return digit_changes <= 1 99 | 100 | def __init__(self, value): 101 | self.value = value 102 | if self.is_valid(value): 103 | self.valid = True 104 | self.MaskX = CPU.extract_bits(value, 11, 2) 105 | self.Mask = CPU.extract_bits(value, 13, 16) 106 | else: 107 | self.valid = False 108 | 109 | def is_mmu_equivalent_to(self, other): 110 | return self.valid == other.valid and \ 111 | self.Mask == other.Mask and \ 112 | self.MaskX == other.MaskX 113 | 114 | def __repr__(self): 115 | return f"PageMask {hex(self.value)} => Mask:{hex(self.Mask)}, MaskX:{hex(self.MaskX)}" 116 | 117 | class PageGrain(CPURegMIPS): 118 | def is_valid(self, value): 119 | return not(CPU.extract_bits(value, 5, 3) != 0x0 or CPU.extract_bits(value, 13, 13) != 0x0) 120 | 121 | def __init__(self, value): 122 | self.value = value 123 | if self.is_valid(value): 124 | self.valid = True 125 | self.MCCause = CPU.extract_bits(value, 0, 5) 126 | self.ASE = CPU.extract_bits(value, 8, 13) 127 | self.S32 = CPU.extract_bits(value, 26, 1) 128 | self.IEC = CPU.extract_bits(value, 27, 1) 129 | self.ESP = CPU.extract_bits(value, 28, 1) 130 | self.ELPA = CPU.extract_bits(value, 29, 1) 131 | self.XIE = CPU.extract_bits(value, 30, 1) 132 | self.RIE = CPU.extract_bits(value, 31, 1) 133 | else: 134 | self.valid = False 135 | 136 | def is_mmu_equivalent_to(self, other): 137 | return (self.valid == other.valid and \ 138 | self.RIE == other.RIE and \ 139 | self.XIE == other.XIE and \ 140 | self.ELPA == other.ELPA and \ 141 | self.ESP == other.ESP) 142 | 143 | def __repr__(self): 144 | return f"PageGrain {hex(self.value)} => RIE:{hex(self.RIE)}, XIE:{hex(self.XIE)}, ELPA:{hex(self.ELPA)}, ESP:{hex(self.ESP)}" 145 | 146 | class SegCtl(CPURegMIPS): 147 | def is_valid(self, value): 148 | return not(CPU.extract_bits(value, 7, 2) != 0x0 or CPU.extract_bits(value, 23, 2) != 0x0) 149 | 150 | class SegCtl0(SegCtl): 151 | def __init__(self, value): 152 | self.value = value 153 | if self.is_valid(value): 154 | self.valid = True 155 | self.CFG0 = CPU.extract_bits(value, 0, 16) 156 | self.CFG1 = CPU.extract_bits(value, 16, 16) 157 | else: 158 | self.valid = False 159 | 160 | def is_mmu_equivalent_to(self, other): 161 | return (self.valid == other.valid and \ 162 | self.CFG0 == other.CFG0 and \ 163 | self.CFG1 == other.CFG1) 164 | 165 | def __repr__(self): 166 | return f"SegCtl0 {hex(self.value)} => CFG0:{hex(self.CFG0)}, CFG1:{hex(self.CFG1)}" 167 | 168 | class SegCtl1(SegCtl): 169 | def __init__(self, value): 170 | self.value = value 171 | if self.is_valid(value): 172 | self.valid = True 173 | self.CFG2 = CPU.extract_bits(value, 0, 16) 174 | self.CFG3 = CPU.extract_bits(value, 16, 16) 175 | else: 176 | self.valid = False 177 | 178 | def is_mmu_equivalent_to(self, other): 179 | return (self.valid == other.valid and \ 180 | self.CFG2 == other.CFG2 and \ 181 | self.CFG3 == other.CFG3) 182 | 183 | def __repr__(self): 184 | return f"SegCtl1 {hex(self.value)} => CFG2:{hex(self.CFG2)}, CFG3:{hex(self.CFG3)}" 185 | 186 | 187 | class SegCtl2(SegCtl): 188 | def __init__(self, value): 189 | self.value = value 190 | if self.is_valid(value): 191 | self.valid = True 192 | self.CFG4 = CPU.extract_bits(value, 0, 16) 193 | self.CFG5 = CPU.extract_bits(value, 16, 16) 194 | else: 195 | self.valid = False 196 | 197 | def is_mmu_equivalent_to(self, other): 198 | return (self.valid == other.valid and \ 199 | self.CFG4 == other.CFG4 and \ 200 | self.CFG5 == other.CFG5) 201 | 202 | def __repr__(self): 203 | return f"SegCtl2 {hex(self.value)} => CFG4:{hex(self.CFG4)}, CFG5:{hex(self.CFG5)}" 204 | 205 | 206 | class PWBase(CPURegMIPS): 207 | def __init__(self, value): 208 | self.value = value 209 | if self.is_valid(value): 210 | self.valid = True 211 | self.PWBase = value 212 | else: 213 | self.valid = False 214 | 215 | def is_mmu_equivalent_to(self, other): 216 | return self.valid == other.valid and self.PWBase == other.PWBase 217 | 218 | def __repr__(self): 219 | return f"PWBase {hex(self.value)} => PWBase:{hex(self.PWBase)}" 220 | 221 | class PWField(CPURegMIPS): 222 | def is_valid(self, value): 223 | if CPU.processor_features["R6_CPU"] and \ 224 | (CPU.extract_bits(value, 24, 6) < 12 or \ 225 | CPU.extract_bits(value, 18, 6) < 12 or \ 226 | CPU.extract_bits(value, 12, 6) < 12 or \ 227 | CPU.extract_bits(value, 6, 6) < 12): 228 | return False 229 | 230 | return CPU.extract_bits(value, 30, 2) == 0x0 231 | 232 | def __init__(self, value): 233 | self.value = value 234 | if self.is_valid(value): 235 | self.valid = True 236 | self.PTEI = CPU.extract_bits(value, 0, 6) 237 | self.PTI = CPU.extract_bits(value, 6, 6) 238 | self.MDI = CPU.extract_bits(value, 12, 6) 239 | self.UDI = CPU.extract_bits(value, 18, 6) 240 | self.GDI = CPU.extract_bits(value, 24, 6) 241 | else: 242 | self.valid = False 243 | 244 | def is_mmu_equivalent_to(self, other): 245 | return self.valid == other.valid and \ 246 | self.PTEI == other.PTEI and \ 247 | self.PTI == other.PTI and \ 248 | self.MDI == other.MDI and \ 249 | self.UDI == other.UDI and \ 250 | self.GDI == other.GDI 251 | 252 | def __repr__(self): 253 | return f"PWField {hex(self.value)} => PTEI:{hex(self.PTEI)}, PTI:{hex(self.PTI)}, MDI:{hex(self.MDI)}, UDI:{hex(self.UDI)}, GDI:{hex(self.GDI)}" 254 | 255 | 256 | class PWSize(CPURegMIPS): 257 | def is_valid(self, value): 258 | if CPU.processor_features["R6_CPU"] and CPU.extract_bits(value, 6, 6) != 1: 259 | return False 260 | 261 | return CPU.extract_bits(value, 30, 2) == 0x0 262 | 263 | def __init__(self, value): 264 | self.value = value 265 | if self.is_valid(value): 266 | self.valid = True 267 | self.PTEW = CPU.extract_bits(value, 0, 6) 268 | self.PTW = CPU.extract_bits(value, 6, 6) 269 | self.MDW = CPU.extract_bits(value, 12, 6) 270 | self.UDW = CPU.extract_bits(value, 18, 6) 271 | self.GDW = CPU.extract_bits(value, 24, 6) 272 | self.PS = CPU.extract_bits(value, 30, 1) 273 | else: 274 | self.valid = False 275 | 276 | def is_mmu_equivalent_to(self, other): 277 | return self.valid == other.valid and \ 278 | self.PTEW == other.PTEW and \ 279 | self.PTW == other.PTW and \ 280 | self.MDW == other.MDW and \ 281 | self.UDW == other.UDW and \ 282 | self.GDW == other.GDW and \ 283 | self.PS == other.PS 284 | 285 | def __repr__(self): 286 | return f"PWSize {hex(self.value)} => PTEW:{hex(self.PTEW)}, PTW:{hex(self.PTW)}, MDW:{hex(self.MDW)}, UDW:{hex(self.UDW)}, GDW:{hex(self.GDW)}, PS:{hex(self.PS)}" 287 | 288 | class Wired(CPURegMIPS): 289 | def is_valid(self, value): 290 | return CPU.extract_bits(value, 0, 16) <= CPU.extract_bits(value, 16, 16) 291 | 292 | def __init__(self, value): 293 | self.value = value 294 | self.Wired = CPU.extract_bits(value, 0, 16) 295 | self.Limit = CPU.extract_bits(value, 16, 16) 296 | if self.is_valid(value): 297 | self.valid = True 298 | else: 299 | self.valid = False 300 | 301 | def is_mmu_equivalent_to(self, other): 302 | return self.valid == other.valid and self.value == other.value 303 | 304 | def __repr__(self): 305 | return f"Wired {hex(self.value)} => Wired:{self.Wired}, Limit:{self.Limit}" 306 | 307 | class PWCtl(CPURegMIPS): 308 | def is_valid(self, value): 309 | return CPU.extract_bits(value, 8, 23) == 0x0 310 | 311 | def __init__(self, value): 312 | self.value = value 313 | if self.is_valid(value): 314 | self.valid = True 315 | self.Psn = CPU.extract_bits(value, 0, 6) 316 | self.HugePg = CPU.extract_bits(value, 6, 1) 317 | self.DPH = CPU.extract_bits(value, 7, 1) 318 | self.PWEn = CPU.extract_bits(value, 31, 1) 319 | else: 320 | self.valid = False 321 | 322 | def is_mmu_equivalent_to(self, other): 323 | return self.valid == other.valid and \ 324 | self.Psn == other.Psn and \ 325 | self.HugePg == other.HugePg and \ 326 | self.DPH == other.DPH and \ 327 | self.PWEn == other.PWEn 328 | 329 | def __repr__(self): 330 | return f"PWCtl {hex(self.value)} => Psn:{hex(self.Psn)}, HugePg:{hex(self.HugePg)}, DPH:{hex(self.DPH)}, PWEn:{hex(self.PWEn)}" 331 | 332 | 333 | class Config(CPURegMIPS): 334 | def is_valid(self, value): 335 | return CPU.extract_bits(value, 4, 3) == 0x0 and \ 336 | CPU.extract_bits(value, 31, 1) == 1 337 | 338 | def __init__(self, value): 339 | self.value = value 340 | if self.is_valid(value): 341 | self.valid = True 342 | self.K0 = CPU.extract_bits(value, 0, 3) 343 | self.VI = CPU.extract_bits(value, 3, 1) 344 | self.MT = CPU.extract_bits(value, 7, 3) 345 | self.AR = CPU.extract_bits(value, 10, 3) 346 | self.AT = CPU.extract_bits(value, 13, 2) 347 | self.BE = CPU.extract_bits(value, 15, 1) 348 | self.KU = CPU.extract_bits(value, 25, 3) 349 | self.K23 = CPU.extract_bits(value, 28, 3) 350 | self.M = CPU.extract_bits(value, 31, 1) 351 | else: 352 | self.valid = False 353 | 354 | def is_mmu_equivalent_to(self, other): 355 | return (self.valid == other.valid and \ 356 | self.K0 == other.K0 and \ 357 | self.MT == other.MT and \ 358 | self.KU == other.KU and \ 359 | self.K23 == other.K23) 360 | 361 | def __repr__(self): 362 | return f"Config {hex(self.value)} => K0:{hex(self.K0)}, MT:{hex(self.MT)}, KU:{hex(self.KU)}, K23:{hex(self.K23)}" 363 | 364 | class Config5(CPURegMIPS): 365 | def is_valid(self, value): 366 | return not(CPU.extract_bits(value, 1, 1) != 0x0 or \ 367 | CPU.extract_bits(value, 12, 1) != 0x0 or \ 368 | CPU.extract_bits(value, 14, 13) != 0x0) 369 | 370 | def __init__(self, value): 371 | self.value = value 372 | if self.is_valid(value): 373 | self.valid = True 374 | self.NFExists = CPU.extract_bits(value, 0, 1) 375 | self.UFR = CPU.extract_bits(value, 2, 1) 376 | self.MRP = CPU.extract_bits(value, 3, 1) 377 | self.LLB = CPU.extract_bits(value, 4, 1) 378 | self.MVH = CPU.extract_bits(value, 5, 1) 379 | self.SBRI = CPU.extract_bits(value, 6, 1) 380 | self.VP = CPU.extract_bits(value, 7, 1) 381 | self.FRE = CPU.extract_bits(value, 8, 1) 382 | self.UFE = CPU.extract_bits(value, 9, 1) 383 | self.L2C = CPU.extract_bits(value, 10, 1) 384 | self.DEC = CPU.extract_bits(value, 11, 1) 385 | self.XNP = CPU.extract_bits(value, 13, 1) 386 | self.MSAEn = CPU.extract_bits(value, 27, 1) 387 | self.EVA = CPU.extract_bits(value, 28, 1) 388 | self.CV = CPU.extract_bits(value, 29, 1) 389 | self.K = CPU.extract_bits(value, 30, 1) 390 | self.M = CPU.extract_bits(value, 31, 1) 391 | else: 392 | self.valid = False 393 | 394 | def is_mmu_equivalent_to(self, other): 395 | return (self.valid == other.valid and \ 396 | self.MRP == other.MRP and \ 397 | self.MVH == other.MVH and \ 398 | self.EVA == other.EVA) 399 | 400 | def __repr__(self): 401 | return f"Config5 {hex(self.value)} => MRP:{hex(self.MRP)}, MVH:{hex(self.MVH)}, EVA:{hex(self.EVA)}" 402 | 403 | class CPU(CPUDefault): 404 | @classmethod 405 | def from_cpu_config(cls, cpu_config, **kwargs): 406 | if cpu_config["bits"] == 32: 407 | return CPUMips32(cpu_config) 408 | else: 409 | logging.warning("Sorry :( no support for MIPS64") 410 | exit(1) 411 | 412 | def __init__(self, features): 413 | super(CPU, self).__init__(features) 414 | if self.endianness == "big": 415 | self.processor_features["opcode_unpack_fmt"] = ">I" 416 | else: 417 | self.processor_features["opcode_unpack_fmt"] = "= 2) 421 | CPU.endianness = self.endianness 422 | CPU.processor_features = self.processor_features 423 | CPU.registers_values = self.registers_values 424 | CPU.extract_bits = CPU.extract_bits_little 425 | 426 | class CPUMips32(CPU): 427 | def __init__(self, features): 428 | super(CPUMips32, self).__init__(features) 429 | self.processor_features["ksegs"] = { 430 | "Kseg0": (0x80000000, 0x20000000), # Each Kseg segment start address and size 431 | "Kseg1": (0xA0000000, 0x20000000) 432 | } 433 | self.processor_features["kern_code_phys_end"] = 0x20000000 434 | self.processor_features["opcode_to_mmu_regs"] = { 435 | (4, 1): "ContextConfig", 436 | (5, 0): "PageMask", 437 | (5, 1): "PageGrain", 438 | (5, 2): "SegCtl0", 439 | (5, 3): "SegCtl1", 440 | (5, 4): "SegCtl2", 441 | (5, 5): "PWBase", 442 | (5, 6): "PWField", 443 | (5, 7): "PWSize", 444 | (6, 0): "Wired", 445 | (6, 6): "PWCtl", 446 | (16, 0): "Config", 447 | (16, 5): "Config5", 448 | # (4, 0): "Context", # Registers not used in our analisys 449 | # (16, 4): "Config4", 450 | # (15, 1): "EBase", 451 | # (31, 2): "KScratch0", 452 | # (31, 3): "KScratch1", 453 | # (31, 4): "KScratch2", 454 | # (31, 5): "KScratch3", 455 | # (31, 6): "KScratch4", 456 | # (31, 7): "KScratch5" 457 | } 458 | 459 | self.processor_features["opcode_to_gregs"] = ["ZERO", "AT", "V0", "V1", "A0", "A1", "A2", "A3", 460 | "T0", "T1", "T2", "T3", "T4", "T5", "T6", "T7", 461 | "S0", "S1", "S2", "S3", "S4", "S5", "S6", "S7", 462 | "T8", "T9", "K0", "K1", "GP", "SP", "FP", "RA"] 463 | CPU.processor_features = self.processor_features 464 | CPU.registers_values = self.registers_values 465 | 466 | def parse_opcode(self, instr, page_addr, offset): 467 | opcodes = {} 468 | # Collect MTC0 instructions for MMU registers 469 | if CPUMips32.extract_bits(instr, 21, 11) == 0b01000000100 and \ 470 | CPUMips32.extract_bits(instr, 3, 8) == 0x0: 471 | 472 | sel = CPUMips32.extract_bits(instr, 0, 3) 473 | rd = CPUMips32.extract_bits(instr, 11, 5) 474 | gr = CPUMips32.processor_features["opcode_to_gregs"][CPUMips32.extract_bits(instr, 16, 5)] 475 | 476 | # For each address collect which coprocessor register in involved and which general register it is used to load a value 477 | if (rd, sel) in self.processor_features["opcode_to_mmu_regs"]: 478 | phy_addr = page_addr + offset 479 | mmu_reg = self.processor_features["opcode_to_mmu_regs"][rd, sel] 480 | for kseg_start, kseg_size in CPU.processor_features["ksegs"].values(): 481 | opcodes[phy_addr + kseg_start] = {"register": mmu_reg, 482 | "gpr": [gr], 483 | "f_addr": -1, 484 | "f_parents": set(), 485 | "instruction": "MTC0" 486 | } 487 | return opcodes 488 | 489 | def identify_functions_start(self, addreses): 490 | machine = self.machine.get_miasm_machine() 491 | vm = self.machine.memory.get_miasm_vmmngr() 492 | mdis = machine.dis_engine(bin_stream_vm(vm), loc_db=LocationDB()) 493 | mdis.follow_call = False 494 | mdis.dontdis_retcall = False 495 | instr_len = self.processor_features["instr_len"] 496 | 497 | # Disable MIASM logging 498 | logger = logging.getLogger('asmblock') 499 | logger.disabled = True 500 | 501 | for addr in tqdm(addreses): 502 | # For a passed address disassemble backward as long as we do not 503 | # find a unconditionally return or an invalid instruction 504 | cur_addr = addr 505 | 506 | # Maximum 10000 instructions 507 | instructions = 0 508 | while True and instructions <= 10000: 509 | 510 | try: 511 | asmcode = mdis.dis_instr(cur_addr) 512 | 513 | # ERET/ERETNC/DRET/BC 514 | if asmcode.name in ["BC", "DRET", "ERET", "ERETNC"]: 515 | cur_addr += instr_len 516 | break 517 | 518 | # JR RA/JR.HB RA/J/JIC/B 519 | elif asmcode.name in ["B", "J", "JIC", "JR", "JR.HB", "BAL", "BALC", "BC", "JALR", "JALR.HB","JALX","JIALC", "JIC"]: 520 | cur_addr += instr_len * 2 521 | break 522 | 523 | else: 524 | cur_addr -= instr_len 525 | instructions += 1 526 | 527 | except IndexError: 528 | # Stop if found an invalid instruction 529 | cur_addr += instr_len 530 | break 531 | 532 | if instructions < 10000: 533 | addreses[addr]["f_addr"] = cur_addr 534 | 535 | del vm 536 | 537 | class PhysicalMemory(PhysicalMemoryDefault): 538 | def get_miasm_vmmngr(self): 539 | if self._miasm_vm is not None: 540 | return self._miasm_vm 541 | 542 | vm = Vm() 543 | # Due to the existence of two Kernel unmapped segments which map kernel physical memory we need to instruct 544 | # MIASM to see both of them 545 | for region_def in tqdm(self._memregions): 546 | if region_def["start"] == 0: 547 | for kseg_name, kseg_addr_size in CPU.processor_features["ksegs"].items(): 548 | kseg_addr, kseg_size = kseg_addr_size 549 | vm.add_memory_page(region_def["start"] + kseg_addr, PAGE_READ | PAGE_WRITE | PAGE_EXEC, 550 | region_def["fd"].read(kseg_size), kseg_name) 551 | region_def["fd"].seek(0) 552 | break 553 | 554 | self._miasm_vm = vm 555 | return self._miasm_vm 556 | 557 | 558 | class MMU(MMUDefault): 559 | PAGE_SIZE = 4096 560 | extract_bits = MMUDefault.extract_bits_little 561 | 562 | 563 | class MIPS32(MMU): 564 | pass 565 | 566 | 567 | class MMUShell(MMUShellDefault): 568 | def __init__(self, completekey='tab', stdin=None, stdout=None, machine={}): 569 | super(MMUShell, self).__init__(completekey, stdin, stdout, machine) 570 | 571 | if not self.data: 572 | self.data = Data(is_mem_parsed = False, 573 | is_registers_found = False, 574 | opcodes = {}, 575 | regs_values = {}) 576 | 577 | def do_parse_memory(self, args): 578 | """Find MMU related opcodes in dump""" 579 | if self.data.is_mem_parsed: 580 | logger.warning("Memory already parsed") 581 | return 582 | 583 | self.parse_memory() 584 | self.data.is_mem_parsed = True 585 | 586 | def parse_memory(self): 587 | logger.info("Look for opcodes related to MMU setup...") 588 | parallel_results = self.machine.apply_parallel(self.machine.mmu.PAGE_SIZE, self.machine.cpu.parse_opcodes_parallel, max_address=self.machine.cpu.processor_features["kern_code_phys_end"]) 589 | 590 | opcodes = {} 591 | logger.info("Reaggregate threads data...") 592 | for result in parallel_results: 593 | opcodes.update(result.get()) 594 | 595 | self.data.opcodes = opcodes 596 | 597 | def do_find_registers_values(self, arg): 598 | """Find and execute MMU related functions inside the memory dump in order to extract MMU registers values""" 599 | 600 | if not self.data.is_mem_parsed: 601 | logging.warning("First parse the dump!") 602 | return 603 | 604 | if self.data.is_registers_found: 605 | logging.warning("Registers already searched") 606 | return 607 | 608 | logging.info("This analysis could be extremely slow!") 609 | logging.info("Use heuristics to find function addresses...") 610 | self.machine.cpu.identify_functions_start(self.data.opcodes) 611 | 612 | logging.info("Identify register values using data flow analysis...") 613 | 614 | # We use data flow analysis and merge the results 615 | dataflow_values = self.machine.cpu.find_registers_values_dataflow(self.data.opcodes, zero_registers=["ZERO"]) 616 | 617 | filtered_values = defaultdict(set) 618 | for register, values in dataflow_values.items(): 619 | for value in values: 620 | reg_obj = CPURegMIPS.get_register_obj(register, value) 621 | if reg_obj.valid: 622 | filtered_values[register].add(reg_obj) 623 | 624 | # Add default values 625 | for register, value in self.machine.cpu.registers_values.items(): 626 | if register not in self.machine.cpu.processor_features["opcode_to_mmu_regs"].values(): 627 | continue 628 | 629 | reg_obj = CPURegMIPS.get_register_obj(register, value) 630 | if reg_obj.valid and all([not reg_obj.is_mmu_equivalent_to(x) for x in filtered_values[register]]): 631 | filtered_values[register].add(reg_obj) 632 | 633 | self.data.regs_values = filtered_values 634 | self.data.is_registers_found = True 635 | 636 | def do_show_registers(self, args): 637 | """Show recovered registers values""" 638 | if not self.data.is_registers_found: 639 | logging.info("Please, find them first!") 640 | return 641 | 642 | for registers in sorted(self.data.regs_values.keys()): 643 | for register in self.data.regs_values[registers]: 644 | print(register) 645 | 646 | 647 | class MMUShellGTruth(MMUShell): 648 | def do_show_registers_gtruth(self, args): 649 | """Show recovered registers values and compare them with the ground truth""" 650 | if not self.data.is_registers_found: 651 | logging.info("Please, find them first!") 652 | return 653 | 654 | # Collect ground truth values as last register values loaded or default ones 655 | mmu_regs = self.machine.cpu.processor_features["opcode_to_mmu_regs"].values() 656 | gvalues = {} 657 | for reg_name in mmu_regs: 658 | if reg_name in self.gtruth: 659 | last_reg_value = sorted(self.gtruth[reg_name].keys(), key=lambda x: self.gtruth[reg_name][x][1])[-1] 660 | gvalues[reg_name] = CPURegMIPS.get_register_obj(reg_name, last_reg_value) 661 | elif reg_name in self.machine.cpu.registers_values: 662 | gvalues[reg_name] = CPURegMIPS.get_register_obj(reg_name, self.machine.cpu.registers_values[reg_name]) 663 | 664 | 665 | tps = defaultdict(list) 666 | fps = defaultdict(list) 667 | fns = {} 668 | 669 | tps_count = 0 670 | fps_count = 0 671 | 672 | # Check between value recovered with dataflow analisys 673 | for register, register_obj in gvalues.items(): 674 | tmp_fps = [] 675 | tmp_fps_count = 0 676 | for found_value in self.data.regs_values[register]: 677 | 678 | if register_obj.is_mmu_equivalent_to(found_value): 679 | # Count only one TP per register 680 | if register not in tps: 681 | tps_count += 1 682 | tps[register].append(found_value) 683 | else: 684 | # Count only FP not equivalent among them 685 | if all([not found_value.is_mmu_equivalent_to(x) for x in fps[register]]): 686 | tmp_fps_count += 1 687 | tmp_fps.append(found_value) 688 | 689 | # Add false negatives 690 | if register not in tps: 691 | fns[register] = register_obj 692 | else: # Add false positives only if it is not a false negative 693 | fps[register] = tmp_fps 694 | fps_count += tmp_fps_count 695 | 696 | 697 | print("\nTrue positives") 698 | pprint(tps) 699 | 700 | print("\nFalse positives") 701 | pprint(fps) 702 | 703 | print("\nFalse negatives") 704 | pprint(fns) 705 | print(f"\nTP:{tps_count}, FP:{fps_count}, FN:{len(fns)}") 706 | -------------------------------------------------------------------------------- /architectures/ppc.py: -------------------------------------------------------------------------------- 1 | from architectures.generic import Machine as MachineDefault 2 | from architectures.generic import CPU as CPUDefault 3 | from architectures.generic import PhysicalMemory as PhysicalMemoryDefault 4 | from architectures.generic import MMUShell as MMUShellDefault 5 | from architectures.generic import MMU as MMUDefault 6 | from architectures.generic import CPUReg 7 | import logging 8 | from prettytable import PrettyTable 9 | from dataclasses import dataclass 10 | from tqdm import tqdm 11 | from struct import unpack, iter_unpack 12 | from collections import defaultdict 13 | from miasm.analysis.machine import Machine as MIASMMachine 14 | from miasm.core.bin_stream import bin_stream_vm 15 | from miasm.core.locationdb import LocationDB 16 | from copy import deepcopy 17 | from pprint import pprint 18 | from time import sleep 19 | from random import uniform 20 | from copy import deepcopy, copy 21 | from math import log2 22 | import portion 23 | 24 | logger = logging.getLogger(__name__) 25 | 26 | @dataclass 27 | class Data: 28 | is_mem_parsed: bool 29 | is_registers_found: bool 30 | opcodes: dict 31 | regs_values: dict 32 | htables: dict 33 | 34 | 35 | class CPURegPPC(CPUReg): 36 | @classmethod 37 | def get_register_obj(cls, reg_name, value): 38 | 39 | # It exists multiple BAT registers 40 | if "BAT" in reg_name: 41 | if "U" in reg_name: 42 | return BATU(value, reg_name) 43 | else: 44 | return BATL(value, reg_name) 45 | else: 46 | return SDR1(value) 47 | 48 | 49 | class SDR1(CPURegPPC): 50 | def is_valid(self, value): 51 | return CPU.extract_bits(value, 16, 7) == 0x0 52 | 53 | def __init__(self, value): 54 | self.value = value 55 | if self.is_valid(value): 56 | self.valid = True 57 | self.address = CPU.extract_bits(value, 0, 16) << 16 58 | self.size = 1 << (int(log2(CPU.extract_bits(value, 23, 9) + 1)) + 16) 59 | else: 60 | self.valid = False 61 | 62 | def __repr__(self): 63 | return f"SDR1 {hex(self.value)} => Address:{hex(self.address)}, Size:{hex(self.size)}" 64 | 65 | 66 | class BATU(CPURegPPC): 67 | def is_valid(self, value): 68 | return CPU.extract_bits(value, 15, 4) == 0x0 69 | 70 | def __init__(self, value, name): 71 | self.bat_name = name 72 | self.value = value 73 | if self.is_valid(value): 74 | self.valid = True 75 | self.bepi = CPU.extract_bits(value, 0, 15) 76 | self.bl = CPU.extract_bits(value, 19, 11) 77 | self.vs = CPU.extract_bits(value, 30, 1) 78 | self.vp = CPU.extract_bits(value, 31, 1) 79 | else: 80 | self.valid = False 81 | 82 | def __repr__(self): 83 | return f"{self.bat_name} {hex(self.value)} => BEPI:{hex(self.bepi)}, BL:{hex(self.bl)}, VS:{hex(self.bl)}, VP:{hex(self.bl)}" 84 | 85 | def __eq__(self, other): 86 | return self.value == other.value and self.bat_name == other.bat_name 87 | 88 | def __hash__(self): 89 | return hash((self.value, self.bat_name)) 90 | 91 | 92 | class BATL(CPURegPPC): 93 | def is_valid(self, value): 94 | return CPU.extract_bits(value, 15, 10) == 0x0 and CPU.extract_bits(value, 29, 1) == 0 95 | 96 | def __init__(self, value, name): 97 | self.bat_name = name 98 | self.value = value 99 | if self.is_valid(value): 100 | self.valid = True 101 | self.brpn = CPU.extract_bits(value, 0, 15) 102 | self.pp = CPU.extract_bits(value, 30, 2) 103 | else: 104 | self.valid = False 105 | 106 | def __repr__(self): 107 | return f"{self.bat_name} {hex(self.value)} => BRPN:{hex(self.brpn)}, PP:{hex(self.pp)}" 108 | 109 | def __eq__(self, other): 110 | return self.value == other.value and self.bat_name == other.bat_name 111 | 112 | def __hash__(self): 113 | return hash((self.value, self.bat_name)) 114 | 115 | class PTE32: 116 | entry_name = "PTE32" 117 | labels = ["Address:", "VSID:", "RPN:", "API:" "Secondary hash:", "Referenced:", "Changed:", "WIMG:", "PP:"] 118 | size = 4 119 | addr_fmt = "0x{:08x}" 120 | 121 | def __init__(self, address, vsid, h, api, rpn, r, c, wimg, pp): 122 | self.address = address 123 | self.vsid = vsid 124 | self.h = h 125 | self.api = api 126 | self.rpn = rpn 127 | self.r = r 128 | self.c = c 129 | self.wimg = wimg 130 | self.pp = pp 131 | 132 | def __hash__(self): 133 | return hash(self.entry_name) 134 | 135 | def __repr__(self): 136 | e_resume = self.entry_resume_stringified() 137 | return str([self.labels[i] + " " + str(e_resume[i]) for i in range(len(self.labels))]) 138 | 139 | def entry_resume(self): 140 | return [self.address, 141 | hex(self.vsid), 142 | hex(self.rpn), 143 | hex(self.api), 144 | bool(self.h), 145 | bool(self.r), 146 | bool(self.c), 147 | bin(self.wimg), 148 | hex(self.pp) 149 | ] 150 | 151 | def entry_resume_stringified(self): 152 | res = self.entry_resume() 153 | res[0] = self.addr_fmt.format(res[0]) 154 | for idx, r in enumerate(res[1:], start=1): 155 | res[idx] = str(r) 156 | return res 157 | 158 | 159 | class HashTable: 160 | def __init__(self, address, size, ptegs): 161 | self.address = address 162 | self.size = size 163 | self.ptegs = ptegs 164 | 165 | table_fields = ["Entry address", "VSID", "RPN", "API", "Secondary hash","Referenced", "Changed", "WIMG", "PP"] 166 | addr_fmt = "0x{:08x}" 167 | 168 | def __repr__(self): 169 | table = PrettyTable() 170 | table.field_names = self.table_fields 171 | 172 | for pteg in self.ptegs.values(): 173 | for entry_obj in pteg: 174 | entry_resume = entry_obj.entry_resume() 175 | entry_resume[0] = self.addr_fmt.format(entry_resume[0]) 176 | table.add_row(entry_resume) 177 | 178 | table.sortby="Entry address" 179 | return str(table) 180 | 181 | 182 | class PhysicalMemory(PhysicalMemoryDefault): 183 | pass 184 | 185 | class CPU(CPUDefault): 186 | @classmethod 187 | def from_cpu_config(cls, cpu_config, **kwargs): 188 | if cpu_config["bits"] == 32: 189 | return CPUPPC32(cpu_config) 190 | else: 191 | logging.warning("Sorry :( no support for POWER") 192 | exit(1) 193 | 194 | def __init__(self, features): 195 | super(CPU, self).__init__(features) 196 | if self.endianness == "big": 197 | self.processor_features["opcode_unpack_fmt"] = ">I" 198 | CPU.extract_bits = CPU.extract_bits_big 199 | else: 200 | self.processor_features["opcode_unpack_fmt"] = "> 25 536 | htaborg_down = CPUPPC32.extract_bits_little(htable_addr >> 16, 0, 9) 537 | 538 | # Compute the AND and OR with SDR1 fields 539 | selector_middle = (hash_value & htabmask) | htaborg_down 540 | 541 | # Calculate the PTEG physical truncated address 542 | incomplete_pteg_addr = (htaborg_up << 25) + (selector_middle << 16) 543 | 544 | return (incomplete_pteg_addr >> 16 << 16) == (pteg_addr_entry >> 16 << 16) 545 | 546 | 547 | class MMUShell(MMUShellDefault): 548 | def __init__(self, completekey='tab', stdin=None, stdout=None, machine={}): 549 | super(MMUShell, self).__init__(completekey, stdin, stdout, machine) 550 | 551 | if not self.data: 552 | self.data = Data(is_mem_parsed = False, 553 | is_registers_found = False, 554 | opcodes = {}, 555 | regs_values = {}, 556 | htables = {} 557 | ) 558 | 559 | def do_parse_memory(self, args): 560 | """Parse memory to find opcode MMU related and hash tables""" 561 | if self.data.is_mem_parsed: 562 | logger.warning("Memory already parsed") 563 | return 564 | 565 | self.parse_memory() 566 | self.data.is_mem_parsed = True 567 | 568 | def parse_memory(self): 569 | # Collect opcodes and hash table of the minium size 570 | fragments, self.data.opcodes = self.machine.mmu.collect_htable_framents_opcodes() 571 | 572 | # Glue hash table 573 | htables = self.machine.mmu.glue_htable_fragments(fragments) 574 | 575 | # Filtering results 576 | self.data.htables = self.machine.mmu.filter_htables(htables) 577 | 578 | def do_show_hashtables(self, args): 579 | """Show hash tables found""" 580 | if not self.data.is_mem_parsed: 581 | logger.warning("Please, parse the memory first") 582 | return 583 | 584 | table = PrettyTable() 585 | table.field_names = ["Address", "Size"] 586 | 587 | for size in sorted(self.data.htables.keys()): 588 | for htable in self.data.htables[size]: 589 | table.add_row([hex(htable.address), hex(size)]) 590 | 591 | print(table) 592 | 593 | def do_find_registers_values(self, arg): 594 | """Find and execute MMU related functions inside the memory dump in order to extract MMU registers values""" 595 | 596 | if not self.data.is_mem_parsed: 597 | logging.warning("First parse the dump!") 598 | return 599 | 600 | if self.data.is_registers_found: 601 | logging.warning("Registers already searched") 602 | return 603 | 604 | logging.info("This analysis could be extremely slow!") 605 | logging.info("Use heuristics to find function addresses...") 606 | self.machine.cpu.identify_functions_start(self.data.opcodes) 607 | 608 | logging.info("Identify register values using data flow analysis...") 609 | 610 | # We use data flow analysis and merge the results 611 | dataflow_values = self.machine.cpu.find_registers_values_dataflow(self.data.opcodes, zero_registers=["ZERO"]) 612 | 613 | filtered_values = defaultdict(set) 614 | for register, values in dataflow_values.items(): 615 | for value in values: 616 | reg_obj = CPURegPPC.get_register_obj(register, value) 617 | if reg_obj.valid: 618 | filtered_values[register].add(reg_obj) 619 | 620 | # Add default values 621 | for register, value in self.machine.cpu.registers_values.items(): 622 | if register not in self.machine.cpu.processor_features["opcode_to_mmu_regs"].values(): 623 | continue 624 | 625 | reg_obj = CPURegPPC.get_register_obj(register, value) 626 | if reg_obj.valid and all([not reg_obj.is_mmu_equivalent_to(x) for x in filtered_values[register]]): 627 | filtered_values[register].add(reg_obj) 628 | 629 | self.data.regs_values = filtered_values 630 | self.data.is_registers_found = True 631 | 632 | def do_show_registers(self, args): 633 | """Show registers values found""" 634 | if not self.data.is_registers_found: 635 | logging.info("Please, find them first!") 636 | return 637 | 638 | for registers in sorted(self.data.regs_values.keys()): 639 | for register in self.data.regs_values[registers]: 640 | print(register) 641 | 642 | def do_show_hashtable(self, arg): 643 | 'Parse a Hash Table of a given size' 644 | 'Usage: show_hashtable ADDRESS size' 645 | 646 | arg = arg.split() 647 | if len(arg) < 2: 648 | logging.error("Missing parameter") 649 | return 650 | 651 | try: 652 | address = self.parse_int(arg[0]) 653 | size = self.parse_int(arg[1]) 654 | except ValueError: 655 | logging.error("Invalid format") 656 | return 657 | 658 | if address is None: 659 | logging.error("Invalid address format") 660 | return 661 | 662 | if address not in self.machine.memory: 663 | logging.error("Address not in memory address space") 664 | return 665 | 666 | valid_sizes = [65536, 131072, 262144, 524288, 1048576, 2097152, 4194304, 8388608, 16777216, 33554432] 667 | if size not in valid_sizes: 668 | logging.error(f"Invalid order table. Valid sizes are {valid_sizes}") 669 | return 670 | 671 | frame_buf = self.machine.memory.get_data(address, size) 672 | table = self.machine.mmu.parse_hash_table(frame_buf, size, address) 673 | if table is None: 674 | logging.warning("Table not present or malformed") 675 | else: 676 | print(table) 677 | 678 | class MMUShellGTruth(MMUShell): 679 | 680 | def do_show_hashtables_gtruth(self, args): 681 | """Show hash tables found and compare them with the ground truth""" 682 | if not self.data.is_mem_parsed: 683 | logger.warning("Please, parse the memory first") 684 | return 685 | 686 | table = PrettyTable() 687 | table.field_names = ["Address", "Size", "Found", "Correct size", "First seen", "Last seen"] 688 | 689 | # Collect valid true table 690 | valids = {} 691 | for sdr1_value, sdr1_data in self.gtruth["SDR1"].items(): 692 | sdr1_obj = SDR1(sdr1_value) 693 | if not sdr1_obj.valid: 694 | continue 695 | valids[sdr1_obj.address] = [sdr1_obj.size, sdr1_data["first_seen"], sdr1_data["last_seen"]] 696 | 697 | # MMUShell found values 698 | found = {} 699 | for size in self.data.htables: 700 | for table_obj in self.data.htables[size]: 701 | found[table_obj.address] = [table_obj.size, "False positive", "False positive"] 702 | 703 | already_visited = set() 704 | for k, v in valids.items(): 705 | table.add_row([hex(k), hex(v[0]), "X" if k in found else "", "X" if v[0] == found.get(k, [None,])[0] else "", v[1], v[2]]) 706 | already_visited.add((k, v[0])) 707 | 708 | fps = 0 709 | for k, v in found.items(): 710 | if (k, v[0]) in already_visited: 711 | continue 712 | table.add_row([hex(k), hex(v[0]), "", "", v[1], v[2]]) 713 | fps += 1 714 | 715 | print(table) 716 | print(f"FP: {fps}") 717 | 718 | 719 | def do_show_registers_gtruth(self, args): 720 | """Show registers value retrieved and compare with the ground truth""" 721 | if not self.data.is_registers_found: 722 | logging.info("Please, find them first!") 723 | return 724 | 725 | # Check if the last value of SDR1 was found 726 | last_sdr1 = SDR1(sorted(self.gtruth["SDR1"].keys(), key=lambda x: self.gtruth["SDR1"][x]["last_seen"], reverse=True)[0]) 727 | print(f"Correct SDR1 value: {last_sdr1}") 728 | print("SDR1 correct value... {}FOUND".format("" if last_sdr1 in self.data.regs_values["SDR1"] else "NOT ")) 729 | 730 | # Found last BAT registers used by the system 731 | bats_found = {} 732 | for t in ["I", "D"]: 733 | for i in range(4): 734 | reg_name = t + "BAT" + str(i) 735 | batu_v, batl_v = sorted(self.gtruth[reg_name].keys(), key=lambda x: self.gtruth[reg_name][x][1], reverse=True)[0] 736 | bats_found[reg_name + "U"] = BATU(batu_v, reg_name + "U") 737 | bats_found[reg_name + "L"] = BATL(batl_v, reg_name + "L") 738 | 739 | # Check if values are found 740 | for reg_name in bats_found: 741 | print("{} correct value... {}FOUND\t\t{}".format(reg_name, "" if bats_found[reg_name] in self.data.regs_values[reg_name] else "NOT ", bats_found[reg_name])) 742 | -------------------------------------------------------------------------------- /architectures/riscv.py: -------------------------------------------------------------------------------- 1 | from architectures.generic import Machine as MachineDefault 2 | from architectures.generic import CPU as CPUDefault 3 | from architectures.generic import PhysicalMemory as PhysicalMemoryDefault 4 | from architectures.generic import MMUShell as MMUShellDefault 5 | from architectures.generic import TableEntry, PageTable, MMURadix, PAS, RadixTree 6 | import logging 7 | from collections import defaultdict, deque 8 | from prettytable import PrettyTable 9 | from time import sleep 10 | from tqdm import tqdm 11 | from copy import deepcopy, copy 12 | from random import uniform 13 | from struct import iter_unpack, unpack 14 | from dataclasses import dataclass 15 | import multiprocessing as mp 16 | # import cProfile 17 | import portion 18 | from more_itertools import divide 19 | from IPython import embed 20 | 21 | logger = logging.getLogger(__name__) 22 | 23 | @dataclass 24 | class Data: 25 | is_mem_parsed: bool 26 | is_radix_found: bool 27 | page_tables: dict 28 | data_pages: list 29 | empty_tables: list 30 | reverse_map_tables: list 31 | reverse_map_pages: list 32 | satps: dict 33 | 34 | class SATP: 35 | def __init__(self, satp): 36 | self.satp = satp 37 | self.mode = MMU.extract_bits(satp, 31, 1) 38 | self.asid = MMU.extract_bits(satp, 22, 9) 39 | self.address = MMU.extract_bits(satp, 0, 22) << 12 40 | 41 | def __repr__(self): 42 | print(f"Mode:{self.mode}, ASID:{self.asid}, Address: {hex(self.address)}") 43 | 44 | class Machine(MachineDefault): 45 | def __init__(self, cpu, mmu, memory, **kwargs): 46 | super(Machine, self).__init__(cpu, mmu, memory, **kwargs) 47 | 48 | 49 | class PhysicalMemory(PhysicalMemoryDefault): 50 | pass 51 | 52 | 53 | class CPU(CPUDefault): 54 | @classmethod 55 | def from_cpu_config(cls, cpu_config, **kwargs): 56 | if cpu_config["bits"] == 32: 57 | return CPU32(cpu_config) 58 | else: 59 | return CPU64(cpu_config) 60 | 61 | def __init__(self, features): 62 | super(CPU, self).__init__(features) 63 | CPU.endianness = self.endianness 64 | CPU.extract_bits = CPU.extract_bits_little 65 | 66 | 67 | class CPU32(CPU): 68 | pass 69 | 70 | 71 | class CPU64(CPU): 72 | pass 73 | 74 | ##################################################################### 75 | # 32 bit entries and page table 76 | ##################################################################### 77 | 78 | class TEntry32(TableEntry): 79 | entry_size = 4 80 | entry_name = "TEntry32" 81 | size = 0 82 | labels = ["Address:", "Dirty:", "Accessed:", "Global:", 83 | "User:", "Readable:", "Writable:", "Exectuable:"] 84 | addr_fmt = "0x{:08x}" 85 | 86 | def __hash__(self): 87 | return hash(self.entry_name) 88 | 89 | def __repr__(self): 90 | e_resume = self.entry_resume_stringified() 91 | return str([self.labels[i] + " " + str(e_resume[i]) for i in range(len(self.labels))]) 92 | 93 | def entry_resume(self): 94 | return [self.address, 95 | self.is_dirty_entry(), 96 | self.is_accessed_entry(), 97 | self.is_global_entry(), 98 | not self.is_supervisor_entry(), 99 | self.is_readable_entry(), 100 | self.is_writeble_entry(), 101 | self.is_executable_entry() 102 | ] 103 | 104 | def entry_resume_stringified(self): 105 | res = self.entry_resume() 106 | res[0] = self.addr_fmt.format(res[0]) 107 | for idx, r in enumerate(res[1:], start=1): 108 | res[idx] = str(r) 109 | return res 110 | 111 | def is_dirty_entry(self): 112 | return bool(MMU.extract_bits(self.flags, 7, 1)) 113 | 114 | def is_accessed_entry(self): 115 | return bool(MMU.extract_bits(self.flags, 6, 1)) 116 | 117 | def is_global_entry(self): 118 | return bool(MMU.extract_bits(self.flags, 5, 1)) 119 | 120 | def is_supervisor_entry(self): 121 | return not MMU.extract_bits(self.flags, 4, 1) 122 | 123 | def is_readable_entry(self): 124 | return bool(MMU.extract_bits(self.flags, 1, 1)) 125 | 126 | def is_writeble_entry(self): 127 | return bool(MMU.extract_bits(self.flags, 2, 1)) 128 | 129 | def is_executable_entry(self): 130 | return bool(MMU.extract_bits(self.flags, 3, 1)) 131 | 132 | @staticmethod 133 | def extract_addr(entry): 134 | return MMU.extract_bits(entry, 10, 21) << 12 135 | 136 | def get_permissions(self): 137 | perms = (self.is_readable_entry(), self.is_writeble_entry(), self.is_executable_entry()) 138 | if self.is_supervisor_entry(): 139 | return perms + (False, False, False) 140 | else: 141 | return (False, False, False) + perms 142 | 143 | class PTE4KB32(TEntry32): 144 | entry_name = "PTE4KB32" 145 | size = 1024 * 4 146 | 147 | class PTE4MB(TEntry32): 148 | entry_name = "PTE4MB" 149 | size = 1024 * 1024 * 4 150 | 151 | class PTP32(TEntry32): 152 | entry_name = "PTP32" 153 | size = 0 154 | 155 | class PTP32L0(PTP32): 156 | entry_name = "PTP32L0" 157 | 158 | class PageTableSV32(PageTable): 159 | entry_size = 4 160 | table_fields = ["Entry address", "Pointed address", "Dirty", "Accessed", "Global", 161 | "User", "Readable", "Writable", "Exectuable", "Class"] 162 | addr_fmt = "0x{:08x}" 163 | 164 | def __repr__(self): 165 | table = PrettyTable() 166 | table.field_names = self.table_fields 167 | 168 | for entry_class in self.entries: 169 | for entry_idx, entry_obj in self.entries[entry_class].items(): 170 | entry_addr = self.address + (entry_idx * self.entry_size) 171 | table.add_row([self.addr_fmt.format(entry_addr)] + entry_obj.entry_resume_stringified() + [entry_class.entry_name]) 172 | 173 | table.sortby="Entry address" 174 | return str(table) 175 | 176 | ##################################################################### 177 | # 64 bit entries and page table 178 | ##################################################################### 179 | 180 | class TEntry64(TEntry32): 181 | entry_size = 8 182 | entry_name = "TEntry64" 183 | size = 0 184 | addr_fmt = "0x{:016x}" 185 | 186 | @staticmethod 187 | def extract_addr(entry): 188 | return MMU.extract_bits(entry, 10, 44) << 12 189 | 190 | 191 | class PTE4KB64(TEntry64): 192 | entry_name = "PTE4KB64" 193 | size = 1024 * 4 194 | 195 | class PTE2MB(TEntry64): 196 | entry_name = "PTE2MB" 197 | size = 1024 * 1024 * 2 198 | 199 | class PTE1GB(TEntry64): 200 | entry_name = "PTE1GB" 201 | size = 1024 * 1024 * 1024 202 | 203 | class PTE512GB(TEntry64): 204 | entry_name = "PTE512GB" 205 | size = 1024 * 1024 * 1024 * 512 206 | 207 | class PTP64(TEntry64): 208 | entry_name = "PTP64" 209 | size = 0 210 | 211 | class PTP64L0(PTP64): 212 | entry_name = "PTP64L0" 213 | 214 | class PTP64L1(PTP64): 215 | entry_name = "PTP64L1" 216 | 217 | class PTP64L2(PTP64): 218 | entry_name = "PTP64L2" 219 | 220 | class PageTableSV39(PageTableSV32): 221 | entry_size = 8 222 | addr_fmt = "0x{:016x}" 223 | 224 | class PageTableSV48(PageTableSV32): 225 | entry_size = 8 226 | addr_fmt = "0x{:016x}" 227 | 228 | ################################################################# 229 | # MMU Modes 230 | ################################################################# 231 | 232 | class MMU(MMURadix): 233 | PAGE_SIZE = 4096 234 | extract_bits = MMURadix.extract_bits_little 235 | paging_unpack_format = " A=1 295 | # ####################### 296 | return self.return_leaf_entry(entry) 297 | 298 | def extend_prefix(self, prefix, entry_idx, entry_class): 299 | if entry_class in [PTP32L0, PTE4MB]: 300 | prefix = entry_idx << 22 301 | return prefix 302 | else: 303 | return prefix | (entry_idx << 12) 304 | 305 | def split_vaddr(self, vaddr): 306 | return (((PTP32L0, MMU.extract_bits(vaddr, 22, 10)), 307 | (PTE4KB32, MMU.extract_bits(vaddr, 12, 10)), 308 | ("OFFSET", MMU.extract_bits(vaddr, 0, 12))), \ 309 | ((PTE4MB, MMU.extract_bits(vaddr, 22, 10)), 310 | ("OFFSET", MMU.extract_bits(vaddr, 0, 22)))) 311 | 312 | class SV39(SV32): 313 | paging_unpack_format = " 1: 499 | try: 500 | lvl = self.parse_int(args[1]) 501 | if lvl > self.machine.mmu.radix_levels["global"] - 1: 502 | raise ValueError 503 | except ValueError: 504 | logger.warning("Level must be an integer between 0 and {}".format(str(self.machine.mmu.radix_levels["global"] - 1))) 505 | return 506 | 507 | if lvl == -1: 508 | table_size = self.machine.mmu.PAGE_SIZE 509 | else: 510 | table_size = self.machine.mmu.map_level_to_table_size["global"][lvl] 511 | table_buff = self.machine.memory.get_data(addr, self.machine.mmu.PAGE_SIZE) 512 | invalids, pt_classes, table_obj = self.machine.mmu.parse_frame(table_buff, addr, table_size, lvl) 513 | print(table_obj) 514 | print(f"Invalid entries: {invalids} Table levels: {pt_classes}") 515 | 516 | def parse_memory(self): 517 | logger.info("Look for paging tables...") 518 | parallel_results = self.machine.apply_parallel(self.machine.mmu.PAGE_SIZE, self.machine.mmu.parse_parallel_frame) 519 | logger.info("Reaggregate threads data...") 520 | for result in parallel_results: 521 | page_tables, data_pages, empty_tables = result.get() 522 | 523 | for level in range(self.machine.mmu.radix_levels["global"]): 524 | self.data.page_tables["global"][level].update(page_tables[level]) 525 | 526 | self.data.data_pages.extend(data_pages) 527 | self.data.empty_tables.extend(empty_tables) 528 | 529 | self.data.data_pages = set(self.data.data_pages) 530 | self.data.empty_tables = set(self.data.empty_tables) 531 | 532 | logger.info("Reduce false positives...") 533 | # Remove all tables which point to inexistent table of lower level 534 | for lvl in range(self.machine.mmu.radix_levels["global"] - 1): 535 | 536 | ptr_class = self.machine.mmu.map_ptr_entries_to_levels["global"][lvl] 537 | 538 | referenced_nxt = [] 539 | for table_addr in list(self.data.page_tables["global"][lvl].keys()): 540 | for entry_obj in self.data.page_tables["global"][lvl][table_addr].entries[ptr_class].values(): 541 | if entry_obj.address not in self.data.page_tables["global"][lvl + 1] and \ 542 | entry_obj.address not in self.data.empty_tables: 543 | 544 | # Remove the table 545 | self.data.page_tables["global"][lvl].pop(table_addr) 546 | break 547 | 548 | else: 549 | referenced_nxt.append(entry_obj.address) 550 | 551 | # Remove table not referenced by upper levels 552 | referenced_nxt = set(referenced_nxt) 553 | for table_addr in set(self.data.page_tables["global"][lvl + 1].keys()).difference(referenced_nxt): 554 | self.data.page_tables["global"][lvl + 1].pop(table_addr) 555 | 556 | logger.info("Fill reverse maps...") 557 | for lvl in range(0, self.machine.mmu.radix_levels["global"]): 558 | ptr_class = self.machine.mmu.map_ptr_entries_to_levels["global"][lvl] 559 | page_class = self.machine.mmu.map_datapages_entries_to_levels["global"][lvl][0] # Trick: it has only one dataclass per level 560 | for table_addr, table_obj in self.data.page_tables["global"][lvl].items(): 561 | for entry_obj in table_obj.entries[ptr_class].values(): 562 | self.data.reverse_map_tables[lvl][entry_obj.address].add(table_obj.address) 563 | for entry_obj in table_obj.entries[page_class].values(): 564 | self.data.reverse_map_pages[lvl][entry_obj.address].add(table_obj.address) 565 | 566 | def do_find_radix_trees(self, args): 567 | """Reconstruct radix trees""" 568 | if not self.data.is_mem_parsed: 569 | logging.info("Please, parse the memory first!") 570 | return 571 | 572 | # Some table level was not found... 573 | if not len(self.data.page_tables["global"][0]): 574 | logger.warning("OOPS... no tables in first level... Wrong MMU mode?") 575 | return 576 | 577 | # Go back from PTLn up to top level, the particular form of PTLn permits to find PTL0 578 | logging.info("Go up the paging trees starting from data pages...") 579 | candidates = [] 580 | already_explored = set() 581 | for page_addr in tqdm(self.data.data_pages): 582 | derived_addresses = self.machine.mmu.derive_page_address(page_addr) 583 | for derived_address in derived_addresses: 584 | if derived_address in already_explored: 585 | continue 586 | lvl, addr = derived_address 587 | candidates.extend(self.radix_roots_from_data_page(lvl, addr, self.data.reverse_map_pages, self.data.reverse_map_tables)) 588 | already_explored.add(derived_address) 589 | candidates = list(set(candidates).intersection(self.data.page_tables["global"][0].keys())) 590 | candidates.sort() 591 | 592 | logger.info("Filter candidates...") 593 | satps = {} 594 | for candidate in tqdm(candidates): 595 | 596 | # Obtain radix tree infos 597 | consistency, pas = self.physpace(candidate, self.data.page_tables["global"], self.data.empty_tables) 598 | 599 | # Only consistent trees are valid 600 | if not consistency: 601 | continue 602 | 603 | # Esclude empty trees 604 | if pas.get_kernel_size() == pas.get_user_size() == 0: 605 | continue 606 | 607 | vas = self.virtspace(candidate, 0, self.machine.mmu.top_prefix) 608 | satps[candidate] = RadixTree(candidate, 0, pas, vas) 609 | 610 | self.data.satps = satps 611 | self.data.is_radix_found = True 612 | 613 | def do_show_radix_trees(self, args): 614 | """Show found radix trees""" 615 | if not self.data.is_radix_found: 616 | logging.info("Please, find them first!") 617 | return 618 | 619 | labels = ["Radix address", "First level", "Kernel size (Bytes)", "User size (Bytes)"] 620 | table = PrettyTable() 621 | table.field_names = labels 622 | for satp in self.data.satps.values(): 623 | table.add_row(satp.entry_resume_stringified()) 624 | table.sortby="Radix address" 625 | print(table) 626 | 627 | class MMUShellGTruth(MMUShell): 628 | def do_show_radix_trees_gtruth(self, args): 629 | """Compare found radix trees with the gound truth""" 630 | if not self.data.is_radix_found: 631 | logging.info("Please, find them first!") 632 | return 633 | 634 | # Parse TP SATPs 635 | satp_tp = {} 636 | for satp in self.gtruth["SATP"]: 637 | new_satp = SATP(satp) 638 | if new_satp.address in satp_tp: 639 | continue 640 | 641 | # Validate SATP 642 | if new_satp.address not in self.data.page_tables["global"][0]: 643 | continue 644 | 645 | consistency, pas = self.physpace(new_satp.address, self.data.page_tables["global"], self.data.empty_tables) 646 | if not consistency or (not pas.get_kernel_size() and not pas.get_user_size()): 647 | continue 648 | satp_tp[new_satp.address] = new_satp 649 | 650 | # True positives, false negatives, false positives 651 | tps = set(satp_tp.keys()).intersection(set(self.data.satps.keys())) 652 | fns = set(satp_tp.keys()).difference(set(self.data.satps.keys())) 653 | fps = set(self.data.satps.keys()).difference(set(satp_tp.keys())) 654 | 655 | # Show results 656 | table = PrettyTable() 657 | table.field_names = ["Address", "Found", "First seen", "Last seen"] 658 | for tp in sorted(tps): 659 | table.add_row([hex(tp), 660 | "X", 661 | self.gtruth["SATP"][satp_tp[tp].satp][0], 662 | self.gtruth["SATP"][satp_tp[tp].satp][1]]) 663 | 664 | for fn in sorted(fns): 665 | table.add_row([hex(fn), 666 | "", 667 | self.gtruth["SATP"][satp_tp[fn].satp][0], 668 | self.gtruth["SATP"][satp_tp[fn].satp][1]]) 669 | 670 | for fp in sorted(fps): 671 | table.add_row([hex(fp), 672 | "False positive", 673 | "", 674 | ""]) 675 | 676 | print(table) 677 | print(f"TP:{len(tps)} FN:{len(fns)} FP:{len(fps)}") 678 | 679 | # Export results for next analysis 680 | if len(args): 681 | from pickle import dump as dump_p 682 | with open("dump.mmu", "wb") as f: 683 | results = [{"satp":tp} for tp in sorted(tps)] 684 | dump_p(results, f) -------------------------------------------------------------------------------- /converter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import yaml 4 | import argparse 5 | from sortedcontainers import SortedDict 6 | import json 7 | 8 | def main(): 9 | parser = argparse.ArgumentParser() 10 | parser.add_argument("MACHINE_CONFIG", help="YAML file describing the machine", type=argparse.FileType("r")) 11 | args = parser.parse_args() 12 | 13 | # Load machine config 14 | try: 15 | machine_config = yaml.load(args.MACHINE_CONFIG, Loader=yaml.FullLoader) 16 | args.MACHINE_CONFIG.close() 17 | except Exception as e: 18 | print(f"Malformed YAML file: {e}") 19 | exit(1) 20 | 21 | # Call the exporter 22 | exporter(machine_config) 23 | 24 | def exporter(machine_config): 25 | """Convert dump set into an ELF file containg the physical address space""" 26 | 27 | architecture = machine_config["cpu"]["architecture"] 28 | bits = machine_config["cpu"]["bits"] 29 | endianness = machine_config["cpu"]["endianness"] 30 | prefix = machine_config["memspace"]["ram"][0]["dumpfile"].split(".")[0] 31 | 32 | with open(prefix + ".elf", "wb") as elf_fd: 33 | # Create the ELF header and write it on the file 34 | machine_data = { 35 | "QEMUArchitecture": architecture, 36 | "Uptime": -1, 37 | "CPURegisters": [], 38 | "MemoryMappedDevices": [(x["start"], x["end"] + 1) for x in machine_config["memspace"]["not_ram"]], 39 | "MMUMode": machine_config["mmu"]["mode"] 40 | } 41 | 42 | # Create ELF main header 43 | if architecture == "aarch64": 44 | e_machine = 0xB7 45 | elif architecture == "arm": 46 | e_machine = 0x28 47 | elif architecture == "riscv": 48 | e_machine = 0xF3 49 | elif architecture == "intel": 50 | if bits == 64: 51 | e_machine = 0x3E 52 | else: 53 | e_machine = 0x03 54 | machine_data["CPUSpecifics"] = {"MAXPHYADDR": machine_config["cpu"]["processor_features"]["m_phy"]} 55 | else: 56 | raise Exception("Unsupported architecture") 57 | 58 | e_ehsize = 0x40 59 | e_phentsize = 0x38 60 | elf_h = bytearray(e_ehsize) 61 | elf_h[0x00:0x04] = b'\x7fELF' # Magic 62 | elf_h[0x04] = 2 # Elf type 63 | elf_h[0x05] = 1 if endianness == "little" else 2 # Endianness 64 | elf_h[0x06] = 1 # Version 65 | elf_h[0x10:0x12] = 0x4.to_bytes(2, endianness) # e_type 66 | elf_h[0x12:0x14] = e_machine.to_bytes(2, endianness) # e_machine 67 | elf_h[0x14:0x18] = 0x1.to_bytes(4, endianness) # e_version 68 | elf_h[0x34:0x36] = e_ehsize.to_bytes(2, endianness) # e_ehsize 69 | elf_h[0x36:0x38] = e_phentsize.to_bytes(2, endianness) # e_phentsize 70 | elf_fd.write(elf_h) 71 | 72 | regions = SortedDict() 73 | for region in machine_config["memspace"]["ram"]: 74 | regions[(region["start"], region["end"] + 1)] = region["dumpfile"] 75 | for region in machine_config["memspace"]["not_ram"]: 76 | regions[(region["start"], region["end"] + 1)] = None 77 | 78 | # Write segments in the new file and fill the program header 79 | p_offset = len(elf_h) 80 | offset2p_offset = {} 81 | 82 | for (begin, end), dump_file in regions.items(): 83 | # Not write not RAM regions 84 | if dump_file is None: 85 | offset2p_offset[(begin, end)] = -1 86 | continue 87 | 88 | # Write physical RAM regions 89 | offset2p_offset[(begin, end)] = p_offset 90 | with open(dump_file, "rb") as region_fd: 91 | elf_fd.write(region_fd.read()) 92 | p_offset += (end - begin) 93 | 94 | # Create FOSSIL NOTE segment style 95 | pad = 4 96 | name = "FOSSIL" 97 | n_type = 0xDEADC0DE 98 | name_b = name.encode() 99 | name_b += b"\x00" 100 | namesz = len(name_b).to_bytes(pad, endianness) 101 | name_b += bytes(pad - (len(name_b) % pad)) 102 | 103 | descr_b = json.dumps(machine_data).encode() 104 | descr_b += b"\x00" 105 | descr_b += bytes(pad - (len(descr_b) % pad)) 106 | descrsz = len(descr_b).to_bytes(pad, endianness) 107 | 108 | machine_note = namesz + descrsz + n_type.to_bytes(pad, endianness) + name_b + descr_b 109 | len_machine_note = len(machine_note) 110 | elf_fd.write(machine_note) 111 | 112 | # Create the program header 113 | # Add FOSSIL NOTE entry style 114 | p_header = bytes() 115 | note_entry = bytearray(e_phentsize) 116 | note_entry[0x00:0x04] = 0x4.to_bytes(4, endianness) # p_type 117 | note_entry[0x08:0x10] = p_offset.to_bytes(8, endianness) # p_offset 118 | note_entry[0x20:0x28] = len_machine_note.to_bytes(8, endianness) # p_filesz 119 | 120 | p_offset += len_machine_note 121 | e_phoff = p_offset 122 | p_header += note_entry 123 | 124 | # Add all the segments (ignoring not in RAM pages) 125 | for (begin, end), offset in offset2p_offset.items(): 126 | if offset == -1: 127 | p_filesz = 0 128 | pmask = 6 129 | offset = 0 130 | else: 131 | p_filesz = end - begin 132 | pmask = 7 133 | 134 | segment_entry = bytearray(e_phentsize) 135 | segment_entry[0x00:0x04] = 0x1.to_bytes(4, endianness) # p_type 136 | segment_entry[0x04:0x08] = pmask.to_bytes(4, endianness) # p_flags 137 | segment_entry[0x10:0x18] = begin.to_bytes(8, endianness) # p_vaddr 138 | segment_entry[0x18:0x20] = begin.to_bytes(8, endianness) # p_paddr Original offset 139 | segment_entry[0x28:0x30] = (end - begin).to_bytes(8, endianness) # p_memsz 140 | segment_entry[0x08:0x10] = offset.to_bytes(8, endianness) # p_offset 141 | segment_entry[0x20:0x28] = p_filesz.to_bytes(8, endianness) # p_filesz 142 | 143 | p_header += segment_entry 144 | 145 | # Write the segment header 146 | elf_fd.write(p_header) 147 | s_header_pos = elf_fd.tell() # Last position written (used if we need to write segment header) 148 | e_phnum = len(regions) + 1 149 | 150 | # Modify the ELF header to point to program header 151 | elf_fd.seek(0x20) 152 | elf_fd.write(e_phoff.to_bytes(8, endianness)) # e_phoff 153 | 154 | # If we have more than 65535 segments we have create a special Section entry contains the 155 | # number of program entry (as specified in ELF64 specifications) 156 | if e_phnum < 65536: 157 | elf_fd.seek(0x38) 158 | elf_fd.write(e_phnum.to_bytes(2, endianness)) # e_phnum 159 | else: 160 | elf_fd.seek(0x28) 161 | elf_fd.write(s_header_pos.to_bytes(8, endianness)) # e_shoff 162 | elf_fd.seek(0x38) 163 | elf_fd.write(0xFFFF.to_bytes(2, endianness)) # e_phnum 164 | elf_fd.write(0x40.to_bytes(2, endianness)) # e_shentsize 165 | elf_fd.write(0x1.to_bytes(2, endianness)) # e_shnum 166 | 167 | section_entry = bytearray(0x40) 168 | section_entry[0x2C:0x30] = e_phnum.to_bytes(4, endianness) # sh_info 169 | elf_fd.seek(s_header_pos) 170 | elf_fd.write(section_entry) 171 | 172 | if __name__ == '__main__': 173 | main() 174 | -------------------------------------------------------------------------------- /exporter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import numpy as np 4 | from elftools.elf.elffile import ELFFile 5 | from elftools.elf.segments import NoteSegment 6 | import json 7 | from collections import defaultdict 8 | from struct import iter_unpack 9 | from tqdm import tqdm 10 | from bisect import bisect 11 | import argparse 12 | import json 13 | from compress_pickle import load as load_c 14 | from pickle import load 15 | import traceback 16 | 17 | def main(): 18 | parser = argparse.ArgumentParser() 19 | parser.add_argument("PHY_ELF", help="Dump file in ELF format", type=str) 20 | parser.add_argument("MMU_DATA", help="List of DTBs and MMU configuration registers", type=argparse.FileType("rb")) 21 | args = parser.parse_args() 22 | 23 | # Load session file 24 | try: 25 | mmu_data = load(args.MMU_DATA) 26 | except Exception as e: 27 | print(f"Error: {e}") 28 | exit(1) 29 | 30 | # Load ELF file 31 | elf_dump = ELFDump(args.PHY_ELF) 32 | 33 | # Dump processes 34 | for idx, process_mmu_data in enumerate(tqdm(mmu_data)): 35 | try: 36 | virtspace = get_virtspace(elf_dump, process_mmu_data) 37 | virtspace.export_virtual_memory_elf(f"process.{idx}.elf") 38 | except Exception as e: 39 | print(f"Error during process exporting: {e}") 40 | # print(traceback.format_exc()) 41 | 42 | class IMSimple: 43 | """Fast search in intervals (begin) (end)""" 44 | def __init__(self, keys, values): 45 | self.keys = keys 46 | self.values = values 47 | 48 | def __getitem__(self, x): 49 | idx = bisect(self.keys, x) - 1 50 | begin = self.keys[idx] 51 | if begin <= x < self.values[idx]: 52 | return x - begin 53 | else: 54 | return -1 55 | 56 | def contains(self, x, size): 57 | idx = bisect(self.keys, x) - 1 58 | begin = self.keys[idx] 59 | end = self.values[idx] 60 | if not(begin <= x < end) or x + size >= end: 61 | return -1 62 | else: 63 | return x - begin 64 | 65 | def get_values(self): 66 | return zip(self.keys, self.values) 67 | 68 | def get_extremes(self): 69 | return self.keys[0], self.values[-1] 70 | 71 | class IMData: 72 | """Fast search in intervals (begin), (end, associated data)""" 73 | def __init__(self, keys, values): 74 | self.keys = keys 75 | self.values = values 76 | 77 | def __getitem__(self, x): 78 | idx = bisect(self.keys, x) - 1 79 | begin = self.keys[idx] 80 | end, data = self.values[idx] 81 | if begin <= x < end: 82 | return data 83 | else: 84 | return -1 85 | 86 | def contains(self, x, size): 87 | idx = bisect(self.keys, x) - 1 88 | begin = self.keys[idx] 89 | end, data = self.values[idx] 90 | if not(begin <= x < end) or x + size >= end: 91 | return -1 92 | else: 93 | return data 94 | 95 | def get_values(self): 96 | return zip(self.keys, self.values) 97 | 98 | def get_extremes(self): 99 | return self.keys[0], self.values[-1][0] 100 | 101 | class IMOffsets: 102 | """Fast search in intervals (begin), (end, associated offset)""" 103 | def __init__(self, keys, values): 104 | self.keys = keys 105 | self.values = values 106 | 107 | def __getitem__(self, x): 108 | idx = bisect(self.keys, x) - 1 109 | begin = self.keys[idx] 110 | end, data = self.values[idx] 111 | if begin <= x < end: 112 | return x - begin + data 113 | else: 114 | return -1 115 | 116 | def contains(self, x, size): 117 | """Return the maximum size and the list of intervals""" 118 | idx = bisect(self.keys, x) - 1 119 | begin = self.keys[idx] 120 | end, data = self.values[idx] 121 | if not(begin <= x < end): 122 | return 0, [] 123 | 124 | intervals = [(x, min(end - x, size), x - begin + data)] 125 | if end - x >= size: 126 | return size, intervals 127 | 128 | # The address space requested is bigger than a single interval 129 | start = end 130 | remaining = size - (end - x) 131 | idx += 1 132 | print(start, remaining, idx) 133 | while idx < len(self.values): 134 | begin = self.keys[idx] 135 | end, data = self.values[idx] 136 | 137 | # Virtual addresses must be contigous 138 | if begin != start: 139 | return size - remaining, intervals 140 | 141 | interval_size = min(end - begin, remaining) 142 | intervals.append((start, interval_size, data)) 143 | remaining -= interval_size 144 | if not remaining: 145 | return size, intervals 146 | start += interval_size 147 | idx += 1 148 | 149 | def get_values(self): 150 | return zip(self.keys, self.values) 151 | 152 | def get_extremes(self): 153 | return self.keys[0], self.values[-1][0] 154 | 155 | 156 | class IMOverlapping: 157 | """Fast search in overlapping intervals (begin), (end, [associated 158 | offsets])""" 159 | 160 | def __init__(self, intervals): 161 | limit2changes = defaultdict(lambda: ([], [])) 162 | for idx, (l, r, v) in enumerate(intervals): 163 | assert l < r 164 | limit2changes[l][0].append(v) 165 | limit2changes[r][1].append(v) 166 | self.limits, changes = zip(*sorted(limit2changes.items())) 167 | 168 | self.results = [[]] 169 | s = set() 170 | offsets = {} 171 | res = [] 172 | for idx, (arrivals, departures) in enumerate(changes): 173 | 174 | s.difference_update(departures) 175 | for i in departures: 176 | offsets.pop(i) 177 | 178 | for i in s: 179 | offsets[i] += (self.limits[idx] - self.limits[idx - 1]) 180 | 181 | s.update(arrivals) 182 | for i in arrivals: 183 | offsets[i] = 0 184 | 185 | res.clear() 186 | for k,v in offsets.items(): 187 | res.extend([i + v for i in k]) 188 | self.results.append(res.copy()) 189 | 190 | def __getitem__(self, x): 191 | idx = bisect(self.limits, x) 192 | k = x - self.limits[idx - 1] 193 | return [k + p for p in self.results[idx]] 194 | 195 | def get_values(self): 196 | return zip(self.limits, self.results) 197 | 198 | 199 | class ELFDump: 200 | def __init__(self, elf_filename): 201 | self.filename = elf_filename 202 | self.machine_data = {} 203 | self.p2o = None # Physical to RAM (ELF offset) 204 | self.o2p = None # RAM (ELF offset) to Physical 205 | self.p2mmd = None # Physical to Memory Mapped Devices (ELF offset) 206 | self.elf_buf = np.zeros(0, dtype=np.byte) 207 | self.elf_filename = elf_filename 208 | 209 | with open(self.elf_filename, "rb") as elf_fd: 210 | 211 | # Load the ELF in memory 212 | self.elf_buf = np.fromfile(elf_fd, dtype=np.byte) 213 | elf_fd.seek(0) 214 | 215 | # Parse the ELF file 216 | self.__read_elf_file(elf_fd) 217 | 218 | def __read_elf_file(self, elf_fd): 219 | """Parse the dump in ELF format""" 220 | o2p_list = [] 221 | p2o_list = [] 222 | p2mmd_list = [] 223 | elf_file = ELFFile(elf_fd) 224 | 225 | for segm in elf_file.iter_segments(): 226 | 227 | # NOTES 228 | if isinstance(segm, NoteSegment): 229 | for note in segm.iter_notes(): 230 | 231 | # Ignore NOTE genrated by other softwares 232 | if note["n_name"] != "FOSSIL": 233 | continue 234 | 235 | # At moment only one type of note 236 | if note["n_type"] != 0xdeadc0de: 237 | continue 238 | 239 | # Suppose only one deadcode note 240 | self.machine_data = json.loads(note["n_desc"].rstrip("\x00")) 241 | self.machine_data["Endianness"] = "little" if elf_file.header["e_ident"].EI_DATA == "ELFDATA2LSB" else "big" 242 | self.machine_data["Architecture"] = "_".join(elf_file.header["e_machine"].split("_")[1:]) 243 | else: 244 | # Fill arrays needed to translate physical addresses to file offsets 245 | r_start = segm["p_vaddr"] 246 | r_end = r_start + segm["p_memsz"] 247 | 248 | if segm["p_filesz"]: 249 | p_offset = segm["p_offset"] 250 | p2o_list.append((r_start, (r_end, p_offset))) 251 | o2p_list.append((p_offset, (p_offset + (r_end - r_start), r_start))) 252 | else: 253 | # device_name = "" # UNUSED 254 | for device in self.machine_data["MemoryMappedDevices"]: # Possible because NOTES always the first segment 255 | if device[0] == r_start: 256 | # device_name = device[1] # UNUSED 257 | break 258 | p2mmd_list.append((r_start, r_end)) 259 | 260 | # Debug 261 | # self.p2o_list = p2o_list 262 | # self.o2p_list = o2p_list 263 | # self.p2mmd_list = p2mmd_list 264 | 265 | # Compact intervals 266 | p2o_list = self._compact_intervals(p2o_list) 267 | o2p_list = self._compact_intervals(o2p_list) 268 | p2mmd_list = self._compact_intervals_simple(p2mmd_list) 269 | 270 | self.p2o = IMOffsets(*list(zip(*sorted(p2o_list)))) 271 | self.o2p = IMOffsets(*list(zip(*sorted(o2p_list)))) 272 | self.p2mmd = IMSimple(*list(zip(*sorted(p2mmd_list)))) 273 | 274 | def _compact_intervals_simple(self, intervals): 275 | """Compact intervals if pointer values are contiguos""" 276 | fused_intervals = [] 277 | prev_begin = prev_end = -1 278 | for interval in intervals: 279 | begin, end = interval 280 | if prev_end == begin: 281 | prev_end = end 282 | else: 283 | fused_intervals.append((prev_begin, prev_end)) 284 | prev_begin = begin 285 | prev_end = end 286 | 287 | if prev_begin != begin: 288 | fused_intervals.append((prev_begin, prev_end)) 289 | else: 290 | fused_intervals.append((begin, end)) 291 | 292 | return fused_intervals[1:] 293 | 294 | def _compact_intervals(self, intervals): 295 | """Compact intervals if pointer and pointed values are contigous""" 296 | fused_intervals = [] 297 | prev_begin = prev_end = prev_phy = -1 298 | for interval in intervals: 299 | begin, (end, phy) = interval 300 | if prev_end == begin and prev_phy + (prev_end - prev_begin) == phy: 301 | prev_end = end 302 | else: 303 | fused_intervals.append((prev_begin, (prev_end, prev_phy))) 304 | prev_begin = begin 305 | prev_end = end 306 | prev_phy = phy 307 | 308 | if prev_begin != begin: 309 | fused_intervals.append((prev_begin, (prev_end, prev_phy))) 310 | else: 311 | fused_intervals.append((begin, (end, phy))) 312 | 313 | return fused_intervals[1:] 314 | 315 | def in_ram(self, paddr, size=1): 316 | """Return True if the interval is completely in RAM""" 317 | return self.p2o.contains(paddr, size)[0] == size 318 | 319 | def in_mmd(self, paddr, size=1): 320 | """Return True if the interval is completely in Memory mapped devices space""" 321 | return True if self.p2mmd.contains(paddr, size) != -1 else False 322 | 323 | def get_data(self, paddr, size): 324 | """Return the data at physical address (interval)""" 325 | size_available, intervals = self.p2o.contains(paddr, size) 326 | if size_available != size: 327 | return bytes() 328 | 329 | ret = bytearray() 330 | for interval in intervals: 331 | _, interval_size, offset = interval 332 | ret.extend(self.elf_buf[offset:offset+interval_size].tobytes()) 333 | 334 | return ret 335 | 336 | def get_data_raw(self, offset, size=1): 337 | """Return the data at the offset in the ELF (interval)""" 338 | return self.elf_buf[offset:offset+size].tobytes() 339 | 340 | def get_machine_data(self): 341 | """Return a dict containing machine configuration""" 342 | return self.machine_data 343 | 344 | def get_ram_regions(self): 345 | """Return all the RAM regions of the machine and the associated offset""" 346 | return self.p2o.get_values() 347 | 348 | def get_mmd_regions(self): 349 | """Return all the Memory mapped devices intervals of the machine and the associated offset""" 350 | return self.p2mmd.get_values() 351 | 352 | def get_virtspace(phy, mmu_values): 353 | """Return a virtspace from a physical one""" 354 | architecture = phy.get_machine_data()["Architecture"].lower() 355 | if "riscv" in architecture: 356 | return RISCVTranslator.factory(phy, mmu_values) 357 | elif "x86" in architecture or "386" in architecture: 358 | return IntelTranslator.factory(phy, mmu_values) 359 | else: 360 | raise Exception("Unknown architecture") 361 | 362 | class AddressTranslator: 363 | def __init__(self, dtb, phy): 364 | self.dtb = dtb 365 | self.phy = phy 366 | 367 | # Set machine specifics 368 | if self.wordsize == 4: 369 | self.word_type = np.uint32 370 | if self.phy.machine_data["Endianness"] == "big": 371 | self.word_fmt = ">u4" 372 | else: 373 | self.word_fmt = " physical mappings""" 421 | 422 | table = self.phy.get_data(table_addr, self.table_sizes[lvl]) 423 | if not table: 424 | print(f"Table {hex(table_addr)} size:{self.table_sizes[lvl]} at level {lvl} not in RAM") 425 | return 426 | 427 | for index, entry in enumerate(iter_unpack(self.unpack_fmt, table)): 428 | is_valid, pmask, phy_addr, page_size = self._read_entry(index, entry[0], lvl) 429 | 430 | if not is_valid: 431 | continue 432 | 433 | virt_addr = prefix | (index << self.shifts[lvl]) 434 | pmask = upmask + pmask 435 | 436 | if (lvl == self.total_levels - 1) or page_size: # Last radix level or Leaf 437 | 438 | # Ignore pages not in RAM (some OSs map more RAM than available) and not memory mapped devices 439 | in_ram = self.phy.in_ram(phy_addr, page_size) 440 | in_mmd = self.phy.in_mmd(phy_addr, page_size) 441 | if not in_ram and not in_mmd: 442 | continue 443 | 444 | permissions = self._reconstruct_permissions(pmask) 445 | virt_addr = self._finalize_virt_addr(virt_addr, permissions) 446 | mapping[permissions].append((virt_addr, page_size, phy_addr, in_mmd)) 447 | 448 | # Add only RAM address to the reverse translation P2V 449 | if in_ram and not in_mmd: 450 | if permissions not in reverse_mapping: 451 | reverse_mapping[permissions] = defaultdict(list) 452 | reverse_mapping[permissions][(phy_addr, page_size)].append(virt_addr) 453 | else: 454 | # Lower level entry 455 | self._explore_radixtree(phy_addr, mapping, reverse_mapping, lvl=lvl+1, prefix=virt_addr, upmask=pmask) 456 | 457 | def _compact_intervals_virt_offset(self, intervals): 458 | """Compact intervals if virtual addresses and offsets values are 459 | contigous (virt -> offset)""" 460 | fused_intervals = [] 461 | prev_begin = prev_end = prev_offset = -1 462 | for interval in intervals: 463 | begin, end, phy, _ = interval 464 | 465 | offset = self.phy.p2o[phy] 466 | if offset == -1: 467 | continue 468 | 469 | if prev_end == begin and prev_offset + (prev_end - prev_begin) == offset: 470 | prev_end = end 471 | else: 472 | fused_intervals.append((prev_begin, (prev_end, prev_offset))) 473 | prev_begin = begin 474 | prev_end = end 475 | prev_offset = offset 476 | 477 | if prev_begin != begin: 478 | fused_intervals.append((prev_begin, (prev_end, prev_offset))) 479 | else: 480 | offset = self.phy.p2o[phy] 481 | if offset == -1: 482 | print(f"ERROR!! {phy}") 483 | else: 484 | fused_intervals.append((begin, (end, offset))) 485 | return fused_intervals[1:] 486 | 487 | def _compact_intervals_permissions(self, intervals): 488 | """Compact intervals if virtual addresses are contigous and permissions are equals""" 489 | fused_intervals = [] 490 | prev_begin = prev_end = -1 491 | prev_pmask = (0, 0) 492 | for interval in intervals: 493 | begin, end, _, pmask = interval 494 | if prev_end == begin and prev_pmask == pmask: 495 | prev_end = end 496 | else: 497 | fused_intervals.append((prev_begin, (prev_end, prev_pmask))) 498 | prev_begin = begin 499 | prev_end = end 500 | prev_pmask = pmask 501 | 502 | if prev_begin != begin: 503 | fused_intervals.append((prev_begin, (prev_end, prev_pmask))) 504 | else: 505 | fused_intervals.append((begin, (end, pmask))) 506 | 507 | return fused_intervals[1:] 508 | 509 | def _reconstruct_mappings(self, table_addr, upmask): 510 | # Explore the radix tree 511 | mapping = defaultdict(list) 512 | reverse_mapping = {} 513 | self._explore_radixtree(table_addr, mapping, reverse_mapping, upmask=upmask) 514 | 515 | # Needed for ELF virtual mapping reconstruction 516 | self.reverse_mapping = reverse_mapping 517 | self.mapping = mapping 518 | 519 | # Collect all intervals (start, end+1, phy_page, pmask) 520 | intervals = [] 521 | for pmask, mapping_p in mapping.items(): 522 | if pmask[1] == 0: # Ignore user not accessible pages 523 | print(pmask) 524 | continue 525 | intervals.extend([(x[0], x[0]+x[1], x[2], pmask) for x in mapping_p if not x[3]]) # Ignore MMD 526 | intervals.sort() 527 | 528 | if not intervals: 529 | raise Exception 530 | # Fuse intervals in order to reduce the number of elements to speed up 531 | fused_intervals_v2o = self._compact_intervals_virt_offset(intervals) 532 | fused_intervals_permissions = self._compact_intervals_permissions(intervals) 533 | 534 | # Offset to virtual is impossible to compact in a easy way due to the 535 | # multiple-to-one mapping. We order the array and use bisection to find 536 | # the possible results and a partial 537 | intervals_o2v = [] 538 | for pmasks, d in reverse_mapping.items(): 539 | if pmasks[1] != 0: # Ignore user accessible pages 540 | continue 541 | for k, v in d.items(): 542 | # We have to translate phy -> offset 543 | offset = self.phy.p2o[k[0]] 544 | if offset == -1: # Ignore unresolvable pages 545 | continue 546 | intervals_o2v.append((offset, k[1]+offset, tuple(v))) 547 | intervals_o2v.sort() 548 | 549 | # Fill resolution objects 550 | self.v2o = IMOffsets(*list(zip(*fused_intervals_v2o))) 551 | self.o2v = IMOverlapping(intervals_o2v) 552 | self.pmasks = IMData(*list(zip(*fused_intervals_permissions))) 553 | 554 | def export_virtual_memory_elf(self, elf_filename): 555 | """Create an ELF file containg the virtual address space of the process""" 556 | with open(elf_filename, "wb") as elf_fd: 557 | # Create the ELF header and write it on the file 558 | machine_data = self.phy.get_machine_data() 559 | endianness = machine_data["Endianness"] 560 | machine = machine_data["Architecture"].lower() 561 | 562 | # Create ELF main header 563 | if "aarch64" in machine: 564 | e_machine = 0xB7 565 | elif "arm" in machine: 566 | e_machine = 0x28 567 | elif "riscv" in machine: 568 | e_machine = 0xF3 569 | elif "x86_64" in machine: 570 | e_machine = 0x3E 571 | elif "386" in machine: 572 | e_machine = 0x03 573 | else: 574 | raise Exception("Unknown architecture") 575 | 576 | e_ehsize = 0x40 577 | e_phentsize = 0x38 578 | elf_h = bytearray(e_ehsize) 579 | elf_h[0x00:0x04] = b'\x7fELF' # Magic 580 | elf_h[0x04] = 2 # Elf type 581 | elf_h[0x05] = 1 if endianness == "little" else 2 # Endianness 582 | elf_h[0x06] = 1 # Version 583 | elf_h[0x10:0x12] = 0x4.to_bytes(2, endianness) # e_type 584 | elf_h[0x12:0x14] = e_machine.to_bytes(2, endianness) # e_machine 585 | elf_h[0x14:0x18] = 0x1.to_bytes(4, endianness) # e_version 586 | elf_h[0x34:0x36] = e_ehsize.to_bytes(2, endianness) # e_ehsize 587 | elf_h[0x36:0x38] = e_phentsize.to_bytes(2, endianness) # e_phentsize 588 | elf_fd.write(elf_h) 589 | 590 | # For each pmask try to compact intervals in order to reduce the number of segments 591 | intervals = defaultdict(list) 592 | for (kpmask, pmask), intervals_list in self.mapping.items(): 593 | print(kpmask, pmask) 594 | 595 | if pmask == 0: # Ignore pages not accessible by the process 596 | continue 597 | 598 | intervals[pmask].extend([(x[0], x[0]+x[1], x[2]) for x in intervals_list if not x[3]]) # Ignore MMD 599 | intervals[pmask].sort() 600 | 601 | if len(intervals[pmask]) == 0: 602 | intervals.pop(pmask) 603 | continue 604 | 605 | # Compact them 606 | fused_intervals = [] 607 | prev_begin = prev_end = prev_offset = -1 608 | for interval in intervals[pmask]: 609 | begin, end, phy = interval 610 | 611 | offset = self.phy.p2o[phy] 612 | if offset == -1: 613 | continue 614 | 615 | if prev_end == begin and prev_offset + (prev_end - prev_begin) == offset: 616 | prev_end = end 617 | else: 618 | fused_intervals.append([prev_begin, prev_end, prev_offset]) 619 | prev_begin = begin 620 | prev_end = end 621 | prev_offset = offset 622 | 623 | if prev_begin != begin: 624 | fused_intervals.append([prev_begin, prev_end, prev_offset]) 625 | else: 626 | offset = self.phy.p2o[phy] 627 | if offset == -1: 628 | print(f"ERROR!! {phy}") 629 | else: 630 | fused_intervals.append([begin, end, offset]) 631 | intervals[pmask] = sorted(fused_intervals[1:], key=lambda x: x[1] - x[0], reverse=True) 632 | 633 | # Write segments in the new file and fill the program header 634 | p_offset = len(elf_h) 635 | offset2p_offset = {} # Slow but more easy to implement (best way: a tree sort structure able to be updated) 636 | e_phnum = 0 637 | 638 | for pmask, interval_list in intervals.items(): 639 | e_phnum += len(interval_list) 640 | for idx, interval in enumerate(interval_list): 641 | begin, end, offset = interval 642 | size = end - begin 643 | if offset not in offset2p_offset: 644 | elf_fd.write(self.phy.get_data_raw(offset, size)) 645 | if not self.phy.get_data_raw(offset, size): 646 | print(hex(offset), hex(size)) 647 | new_offset = p_offset 648 | p_offset += size 649 | for page_idx in range(0, size, self.minimum_page): 650 | offset2p_offset[offset + page_idx] = new_offset + page_idx 651 | else: 652 | new_offset = offset2p_offset[offset] 653 | interval_list[idx].append(new_offset) # Assign the new offset in the dest file 654 | 655 | # Create the program header containing all the segments (ignoring not in RAM pages) 656 | e_phoff = elf_fd.tell() 657 | p_header = bytes() 658 | for pmask, interval_list in intervals.items(): 659 | for begin, end, offset, p_offset in interval_list: 660 | p_filesz = end - begin 661 | 662 | # Back convert offset to physical page 663 | p_addr = self.phy.o2p[offset] 664 | assert p_addr != -1 665 | 666 | segment_entry = bytearray(e_phentsize) 667 | segment_entry[0x00:0x04] = 0x1.to_bytes(4, endianness) # p_type 668 | segment_entry[0x04:0x08] = pmask.to_bytes(4, endianness) # p_flags 669 | segment_entry[0x10:0x18] = begin.to_bytes(8, endianness) # p_vaddr 670 | segment_entry[0x18:0x20] = p_addr.to_bytes(8, endianness) # p_paddr Original physical address 671 | segment_entry[0x28:0x30] = p_filesz.to_bytes(8, endianness) # p_memsz 672 | segment_entry[0x08:0x10] = p_offset.to_bytes(8, endianness) # p_offset 673 | segment_entry[0x20:0x28] = p_filesz.to_bytes(8, endianness) # p_filesz 674 | 675 | p_header += segment_entry 676 | 677 | # Write the segment header 678 | elf_fd.write(p_header) 679 | s_header_pos = elf_fd.tell() # Last position written (used if we need to write segment header) 680 | 681 | # Modify the ELF header to point to program header 682 | elf_fd.seek(0x20) 683 | elf_fd.write(e_phoff.to_bytes(8, endianness)) # e_phoff 684 | 685 | # If we have more than 65535 segments we have create a special Section entry contains the 686 | # number of program entry (as specified in ELF64 specifications) 687 | if e_phnum < 65536: 688 | elf_fd.seek(0x38) 689 | elf_fd.write(e_phnum.to_bytes(2, endianness)) # e_phnum 690 | else: 691 | elf_fd.seek(0x28) 692 | elf_fd.write(s_header_pos.to_bytes(8, endianness)) # e_shoff 693 | elf_fd.seek(0x38) 694 | elf_fd.write(0xFFFF.to_bytes(2, endianness)) # e_phnum 695 | elf_fd.write(0x40.to_bytes(2, endianness)) # e_shentsize 696 | elf_fd.write(0x1.to_bytes(2, endianness)) # e_shnum 697 | 698 | section_entry = bytearray(0x40) 699 | section_entry[0x2C:0x30] = e_phnum.to_bytes(4, endianness) # sh_info 700 | elf_fd.seek(s_header_pos) 701 | elf_fd.write(section_entry) 702 | 703 | 704 | class IntelTranslator(AddressTranslator): 705 | @staticmethod 706 | def derive_mmu_settings(mmu_class, regs_dict, mphy): 707 | if mmu_class is IntelAMD64: 708 | dtb = ((regs_dict["cr3"] >> 12) & ((1 << (mphy - 12)) - 1)) << 12 709 | 710 | elif mmu_class is IntelIA32: 711 | dtb = ((regs_dict["cr3"] >> 12) & (1 << 20) - 1) << 12 712 | mphy = min(mphy, 40) 713 | 714 | else: 715 | raise NotImplementedError 716 | 717 | return {"dtb": dtb, 718 | "wp": True, 719 | "ac": False, 720 | "nxe": True, 721 | "smep": False, 722 | "smap": False, 723 | "mphy": mphy 724 | } 725 | 726 | @staticmethod 727 | def derive_translator_class(mmu_mode): 728 | if mmu_mode == "ia64": 729 | return IntelAMD64 730 | elif mmu_mode == "pae": 731 | return NotImplementedError 732 | elif mmu_mode == "ia32": 733 | return IntelIA32 734 | else: 735 | raise NotImplementedError 736 | 737 | @staticmethod 738 | def factory(phy, mmu_values): 739 | machine_data = phy.get_machine_data() 740 | mmu_mode = machine_data["MMUMode"] 741 | mphy = machine_data["CPUSpecifics"]["MAXPHYADDR"] 742 | 743 | translator_c = IntelTranslator.derive_translator_class(mmu_mode) 744 | mmu_settings = IntelTranslator.derive_mmu_settings(translator_c, mmu_values, mphy) 745 | return translator_c(phy=phy, **mmu_settings) 746 | 747 | 748 | def __init__(self, dtb, phy, mphy, wp=False, ac=False, nxe=False, smap=False, smep=False): 749 | super(IntelTranslator, self).__init__(dtb, phy) 750 | self.mphy = mphy 751 | self.wp = wp 752 | self.ac = ac # UNUSED by Fossil 753 | self.smap = smap 754 | self.nxe = nxe 755 | self.smep = smep 756 | self.minimum_page = 0x1000 757 | 758 | print("Creating resolution trees...") 759 | self._reconstruct_mappings(self.dtb, upmask=[[False, True, True]]) 760 | 761 | def _finalize_virt_addr(self, virt_addr, permissions): 762 | return virt_addr 763 | 764 | 765 | class IntelIA32(IntelTranslator): 766 | def __init__(self, dtb, phy, mphy, wp=True, ac=False, nxe=False, smap=False, smep=False): 767 | self.unpack_fmt = "> 12) & ((1 << 20) - 1)) << 12 792 | return True, perms_flags, addr, 0 793 | 794 | # Leaf 795 | else: 796 | if lvl == 0: 797 | addr = (((entry >> 13) & ((1 << (self.mphy - 32)) - 1)) << 32) | (((entry >> 22) & ((1 << 10) - 1)) << 22) 798 | else: 799 | addr = ((entry >> 12) & ((1 << 20) - 1)) << 12 800 | return True, perms_flags, addr, 1 << self.shifts[lvl] 801 | 802 | def _reconstruct_permissions(self, pmask): 803 | k_flags, w_flags, _ = zip(*pmask) 804 | 805 | # Kernel page in user mode 806 | if any(k_flags): 807 | r = True 808 | w = all(w_flags) if self.wp else True 809 | return r << 2 | w << 1 | 1, 0 810 | 811 | # User page in user mode 812 | else: 813 | r = True 814 | w = all(w_flags) 815 | return 0, r << 2 | w << 1 | 1 816 | 817 | class IntelAMD64(IntelTranslator): 818 | def __init__(self, dtb, phy, mphy, wp=True, ac=False, nxe=True, smap=False, smep=False): 819 | self.unpack_fmt = "> 12) & ((1 << (self.mphy - 12)) - 1)) << 12 844 | return True, perms_flags, addr, 0 845 | 846 | # Leaf 847 | else: 848 | addr = ((entry >> self.shifts[lvl]) & ((1 << (self.mphy - self.shifts[lvl])) - 1)) << self.shifts[lvl] 849 | return True, perms_flags, addr, 1 << self.shifts[lvl] 850 | 851 | def _reconstruct_permissions(self, pmask): 852 | k_flags, w_flags, x_flags = zip(*pmask) 853 | 854 | # Kernel page in user mode 855 | if any(k_flags): 856 | r = True 857 | w = all(w_flags) if self.wp else True 858 | x = all(x_flags) if self.nxe else True 859 | 860 | return r << 2 | w << 1 | int(x), 0 861 | 862 | # User page in user mode 863 | else: 864 | r = True 865 | w = all(w_flags) 866 | x = all(x_flags) if self.nxe else True 867 | 868 | return 0, r << 2 | w << 1 | int(x) 869 | 870 | def _finalize_virt_addr(self, virt_addr, permissions): 871 | # Canonical address form 872 | if virt_addr & 0x800000000000: 873 | return self.prefix | virt_addr 874 | else: 875 | return virt_addr 876 | 877 | 878 | class RISCVTranslator(AddressTranslator): 879 | @staticmethod 880 | def derive_mmu_settings(mmu_class, regs_dict): 881 | 882 | dtb = regs_dict["satp"] 883 | return {"dtb": dtb, 884 | "Sum": False, 885 | "mxr": False 886 | } 887 | 888 | @staticmethod 889 | def derive_translator_class(mmu_mode): 890 | if mmu_mode == "sv39": 891 | return RISCVSV39 892 | else: 893 | return RISCVSV32 894 | @staticmethod 895 | def factory(phy, mmu_values): 896 | machine_data = phy.get_machine_data() 897 | mmu_mode = machine_data["MMUMode"] 898 | translator_c = RISCVTranslator.derive_translator_class(mmu_mode) 899 | mmu_settings = RISCVTranslator.derive_mmu_settings(translator_c, mmu_values) 900 | return translator_c(phy=phy, **mmu_settings) 901 | 902 | 903 | def __init__(self, dtb, phy, Sum=True, mxr=True): 904 | super(RISCVTranslator, self).__init__(dtb, phy) 905 | self.Sum = Sum 906 | self.mxr = mxr 907 | self.minimum_page = 0x1000 908 | 909 | print("Creating resolution trees...") 910 | self._reconstruct_mappings(self.dtb, upmask=[[False, True, True, True]]) 911 | 912 | def _finalize_virt_addr(self, virt_addr, permissions): 913 | return virt_addr 914 | 915 | def _reconstruct_permissions(self, pmask): 916 | k_flag, r_flag, w_flag, x_flag = pmask[-1] # No hierarchy 917 | 918 | r = r_flag 919 | if self.mxr: 920 | r |= x_flag 921 | 922 | w = w_flag 923 | x = x_flag 924 | 925 | # Kernel page in user mode 926 | if k_flag: 927 | return r << 2 | w << 1 | int(x), 0 928 | 929 | # User page in user mode 930 | else: 931 | return 0, r << 2 | w << 1 | int(x) 932 | 933 | 934 | class RISCVSV32(RISCVTranslator): 935 | def __init__(self, dtb, phy, Sum, mxr): 936 | self.unpack_fmt = "> 10) & ((1 << 22) - 1)) << 12 960 | # Leaf 961 | if r or w or x or lvl == 1: 962 | return True, perms_flags, addr, 1 << self.shifts[lvl] 963 | else: 964 | # Upper tables pointers 965 | return True, perms_flags, addr, 0 966 | 967 | 968 | class RISCVSV39(RISCVTranslator): 969 | def __init__(self, dtb, phy, Sum, mxr): 970 | self.unpack_fmt = "> 10) & ((1 << 44) - 1)) << 12 994 | # Leaf 995 | if r or w or x or lvl == 2: 996 | return True, perms_flags, addr, 1 << self.shifts[lvl] 997 | else: 998 | # Upper tables pointers 999 | return True, perms_flags, addr, 0 1000 | 1001 | if __name__ == '__main__': 1002 | main() 1003 | -------------------------------------------------------------------------------- /mmushell.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import argparse 3 | import logging 4 | import importlib 5 | import yaml 6 | from cerberus import Validator 7 | 8 | # Set logging configuration 9 | logger = logging.getLogger(__name__) 10 | 11 | # Schema for YAML configuration file 12 | machine_yaml_schema = { 13 | 'cpu': { 14 | 'required': True, 15 | 'type': 'dict', 16 | 'schema': { 17 | 'architecture': {'required': True, 'type': 'string', 'min': 1}, 18 | 'endianness': {'required': True, 'type': 'string', 'min': 1}, 19 | 'bits': {'required': True, 'type': 'integer', 'allowed': [32, 64]}, 20 | 'processor_features': {'required': False, 'type': 'dict'}, 21 | 'registers_values': { 22 | 'required': False, 23 | 'type': 'dict', 24 | 'keysrules': {'type': 'string', 'min': 1}, 25 | 'valuesrules': {'type': 'integer'} 26 | } 27 | } 28 | }, 29 | 'mmu': { 30 | 'required': True, 31 | 'type': 'dict', 32 | 'schema': { 33 | 'mode': {'required': True, 'type': 'string', 'min': 1} 34 | } 35 | }, 36 | 'memspace': { 37 | 'required': True, 38 | 'type': 'dict', 39 | 'schema': { 40 | 'ram': { 41 | 'required': True, 42 | 'type': 'list', 43 | 'minlength': 1, 44 | 'schema': { 45 | 'type': 'dict', 46 | 'schema': { 47 | 'start': {'required': True, 'type': 'integer', 'min': 0, 'max': 0xFFFFFFFFFFFFFFFF}, 48 | 'end': {'required': True, 'type': 'integer', 'min': 0, 'max': 0xFFFFFFFFFFFFFFFF}, 49 | 'dumpfile': {'required': True, 'type': 'string', 'min': 0} 50 | } 51 | } 52 | }, 53 | 'not_ram': { 54 | 'required': True, 55 | 'type': 'list', 56 | 'minlength': 1, 57 | 'schema': { 58 | 'type': 'dict', 59 | 'schema': { 60 | 'start': {'required': True, 'type': 'integer', 'min': 0, 'max': 0xFFFFFFFFFFFFFFFF}, 61 | 'end': {'required': True, 'type': 'integer', 'min': 0, 'max': 0xFFFFFFFFFFFFFFFF}, 62 | } 63 | } 64 | } 65 | } 66 | } 67 | } 68 | 69 | def main(): 70 | # Parse arguments 71 | parser = argparse.ArgumentParser() 72 | parser.add_argument("MACHINE_CONFIG", help="YAML file describing the machine", type=argparse.FileType("r")) 73 | parser.add_argument("--gtruth", help="Ground truth from QEMU registers", type=argparse.FileType("rb", 0), default=None) 74 | parser.add_argument("--session", help="Data file of a previous MMUShell session", type=str, default=None) 75 | parser.add_argument("--debug", help="Enable debug output", action="store_true", default=False) 76 | args = parser.parse_args() 77 | 78 | # Set logging system 79 | fmt = "%(msg)s" 80 | if args.debug: 81 | logging.basicConfig(level=logging.DEBUG, format=fmt) 82 | else: 83 | logging.basicConfig(level=logging.INFO, format=fmt) 84 | 85 | # Load the machine configuration YAML file 86 | try: 87 | machine_config = yaml.load(args.MACHINE_CONFIG, Loader=yaml.FullLoader) 88 | args.MACHINE_CONFIG.close() 89 | except Exception as e: 90 | logger.fatal("Malformed YAML file: {}".format(e)) 91 | exit(1) 92 | 93 | # Validate YAML schema 94 | yaml_validator = Validator(allow_unknown=True) 95 | if not yaml_validator.validate(machine_config, machine_yaml_schema): 96 | logger.fatal("Invalid YAML file. Error:" + str(yaml_validator.errors)) 97 | exit(1) 98 | 99 | # Create the Machine class 100 | try: 101 | architecture_module = importlib.import_module("architectures." + machine_config["cpu"]["architecture"]) 102 | except ModuleNotFoundError: 103 | logger.fatal("Unkown architecture!") 104 | exit(1) 105 | 106 | # Create a Machine starting from the parsed configuration 107 | machine = architecture_module.Machine.from_machine_config(machine_config) 108 | 109 | # Launch the interactive shell 110 | if args.gtruth: 111 | shell = architecture_module.MMUShellGTruth(machine=machine) 112 | else: 113 | shell = architecture_module.MMUShell(machine=machine) 114 | 115 | # Load ground truth (if passed) 116 | if args.gtruth: 117 | shell.load_gtruth(args.gtruth) 118 | 119 | # Load previous data (if passed) 120 | if args.session: 121 | shell.reload_data_from_file(args.session) 122 | 123 | shell.cmdloop() 124 | 125 | 126 | if __name__ == '__main__': 127 | main() 128 | -------------------------------------------------------------------------------- /qemu/README: -------------------------------------------------------------------------------- 1 | INSTALL: 2 | If you use Debian/Ubuntu please install 3 | build-essential git pkg-config libgtk-3-dev python3 python3-dev python3-pip python3-venv 4 | 5 | To run qemu_logger.py or the run_qemu script use ALWAYS the Python 3 virtual env "mmushell_venv" created by the installation script 6 | -------------------------------------------------------------------------------- /qemu/build_qemu: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd .. 3 | mkdir -p src 4 | cd src 5 | 6 | # Download patch and build QEMU 7 | git clone https://github.com/qemu/qemu.git 8 | cd qemu 9 | git checkout tags/v5.0.0 10 | git apply ../../qemu/qemu_v5.0.0.patch 11 | 12 | mkdir build 13 | cd build 14 | ../configure --enable-gtk --target-list=arm-softmmu,aarch64-softmmu,ppc-softmmu,ppc64-softmmu,riscv32-softmmu,riscv64-softmmu,mips-softmmu,mipsel-softmmu,mips64-softmmu,mips64el-softmmu,i386-softmmu,x86_64-softmmu 15 | make CFLAGS="-Warray-bounds=0" -j 8 16 | cd ../../../ 17 | 18 | # Create the virtualenv and install dependencies 19 | if [ ! -d "mmushell_venv" ] ; then 20 | python3 -m venv mmushell_venv 21 | fi 22 | 23 | source ./mmushell_venv/bin/activate 24 | pip3 install -r qemu/requirements.txt 25 | -------------------------------------------------------------------------------- /qemu/qemu_logger.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import errno 4 | from collections import defaultdict 5 | import argparse 6 | import pickle 7 | from qmp import QEMUMonitorProtocol 8 | from signal import signal, SIGINT 9 | from threading import Timer 10 | from datetime import datetime 11 | from copy import deepcopy 12 | 13 | start_time_g = 0 14 | 15 | class CPU: 16 | def __init__(self, mem_base_addr, dump_file_name, debug): 17 | self.regs = defaultdict(dict) 18 | self.debug = debug 19 | self.mem_base_addr = mem_base_addr 20 | self.dump_file_name = dump_file_name 21 | 22 | def parse_log_row(self, data_log, start_time): 23 | if self.debug: 24 | print(data_log) 25 | 26 | time_now = (datetime.now() - start_time).total_seconds() 27 | reg_name, reg_value = data_log.split("=") 28 | reg_value = int(reg_value.strip(), 16) 29 | 30 | if reg_value not in self.regs[reg_name]: 31 | self.regs[reg_name][reg_value] = (time_now, time_now) 32 | else: 33 | first_seen = self.regs[reg_name][reg_value][0] 34 | self.regs[reg_name][reg_value] = (first_seen, time_now) 35 | 36 | def dump_memory(self, qmonitor): 37 | # Grab the memory size and dump the memory 38 | res = qmonitor.cmd("query-memory-size-summary") 39 | memory_size = res["return"]["base-memory"] 40 | qmonitor.cmd("pmemsave", {"val": self.mem_base_addr, 41 | "size": memory_size, 42 | "filename": self.dump_file_name + ".dump"}) 43 | 44 | 45 | class Intel(CPU): 46 | def parse_log_row(self, data_log, start_time): 47 | if self.debug: 48 | print(data_log) 49 | 50 | time_now = (datetime.now() - start_time).total_seconds() 51 | reg_parts = data_log.split("|") 52 | reg_name, reg_value = reg_parts[0].split("=") 53 | reg_value = int(reg_value.strip(), 16) 54 | 55 | if reg_name in ("IDTR", "GDTR"): 56 | limit_value = int(reg_parts[1].split("=")[1].strip(), 16) 57 | reg_value = reg_value << 16 | limit_value 58 | 59 | if reg_value not in self.regs[reg_name]: 60 | self.regs[reg_name][reg_value] = (time_now, time_now) 61 | else: 62 | first_seen = self.regs[reg_name][reg_value][0] 63 | self.regs[reg_name][reg_value] = (first_seen, time_now) 64 | 65 | def dump_memory(self, qmonitor): 66 | res = qmonitor.cmd("query-memory-size-summary") 67 | memory_size = res["return"]["base-memory"] 68 | 69 | # Dump different chunks of memory 70 | qmonitor.cmd("pmemsave", {"val": 0x0, 71 | "size": min(memory_size, 0xC0000000), 72 | "filename": self.dump_file_name + ".dump.0" 73 | }) 74 | if memory_size >= 0xC0000000: 75 | qmonitor.cmd("pmemsave", {"val": 0x100000000, 76 | "size": memory_size - 0xC0000000, 77 | "filename": self.dump_file_name + ".dump.1" 78 | }) 79 | 80 | 81 | class IntelQ35(Intel): 82 | def dump_memory(self, qmonitor): 83 | res = qmonitor.cmd("query-memory-size-summary") 84 | memory_size = res["return"]["base-memory"] 85 | 86 | # Dump different chunks of memory 87 | qmonitor.cmd("pmemsave", {"val": 0x0, 88 | "size": min(memory_size, 0x80000000), 89 | "filename": self.dump_file_name + ".dump.0" 90 | }) 91 | if memory_size >= 0x80000000: 92 | qmonitor.cmd("pmemsave", {"val": 0x100000000, 93 | "size": memory_size - 0x80000000, 94 | "filename": self.dump_file_name + ".dump.1" 95 | }) 96 | 97 | 98 | class PPC(CPU): 99 | def __init__(self, mem_base_addr, dump_file_name, debug): 100 | super(PPC, self).__init__(mem_base_addr, dump_file_name, debug) 101 | dict_proto = {"U": {"value": 0, "modified": False}, "L": {"value": 0, "modified": False}} 102 | self._BATS = {x: deepcopy(dict_proto) for x in 103 | ["DBAT0", "DBAT1", "DBAT2", "DBAT3", "IBAT0", "IBAT1", "IBAT2", "IBAT3"]} 104 | 105 | def parse_log_row(self, data_log, start_time): 106 | if self.debug: 107 | print(data_log) 108 | 109 | time_now = (datetime.now() - start_time).total_seconds() 110 | keys_values = data_log.split("|") 111 | reg_name, reg_value = keys_values[0].split("=") 112 | reg_value = int(reg_value.strip(), 16) 113 | 114 | if reg_name == "SDR1": 115 | _, vsid = keys_values[1].split("=") 116 | if reg_value not in self.regs[reg_name]: 117 | if vsid.strip() == "-1": 118 | self.regs[reg_name][reg_value] = {"first_seen": time_now, "last_seen": time_now, "vsids": {}} 119 | else: 120 | vsid = int(vsid.strip(), 16) 121 | self.regs[reg_name][reg_value] = {"first_seen": time_now, "last_seen": time_now, 122 | "vsids": {vsid: (time_now, time_now)}} 123 | else: 124 | self.regs[reg_name][reg_value]["last_seen"] = time_now 125 | if vsid.strip() != "-1": 126 | vsid = int(vsid.strip(), 16) 127 | if vsid not in self.regs[reg_name][reg_value]["vsids"]: 128 | self.regs[reg_name][reg_value]["vsids"][vsid] = (time_now, time_now) 129 | else: 130 | first_seen = self.regs[reg_name][reg_value]["vsids"][vsid][0] 131 | self.regs[reg_name][reg_value]["vsids"][vsid] = (first_seen, time_now) 132 | pass 133 | elif "BAT" in reg_name: 134 | reg_group = reg_name[0:5] 135 | reg_part = reg_name[5:] 136 | 137 | # We suppose that XBATYU and XBATYL are both updated when one of them is updated 138 | self._BATS[reg_group][reg_part]["value"] = reg_value 139 | self._BATS[reg_group][reg_part]["modified"] = True 140 | 141 | if self._BATS[reg_group]["U"]["modified"] and self._BATS[reg_group]["L"]["modified"]: 142 | regs_values = (self._BATS[reg_group]["U"]["value"], self._BATS[reg_group]["L"]["value"]) 143 | if regs_values not in self.regs[reg_group]: 144 | self.regs[reg_group][regs_values] = (time_now, time_now) 145 | else: 146 | first_seen = self.regs[reg_group][regs_values][0] 147 | self.regs[reg_group][regs_values] = (first_seen, time_now) 148 | 149 | self._BATS[reg_group]["U"]["modified"] = False 150 | self._BATS[reg_group]["L"]["modified"] = False 151 | 152 | else: 153 | if reg_value not in self.regs[reg_name]: 154 | self.regs[reg_name][reg_value] = (time_now, time_now) 155 | else: 156 | first_seen = self.regs[reg_name][reg_value][0] 157 | self.regs[reg_name][reg_value] = (first_seen, time_now) 158 | 159 | 160 | class Arm(CPU): 161 | pass 162 | 163 | 164 | class ArmVirtSecure(CPU): 165 | def __init__(self, mem_base_addr, dump_file_name, debug): 166 | super(ArmVirtSecure, self).__init__(mem_base_addr, dump_file_name, debug) 167 | self.secure_mem_base_addr = 0xe000000 168 | self.secure_memory_size = 0x01000000 169 | 170 | def dump_memory(self, qmonitor): 171 | super(ArmVirtSecure, self).dump_memory(qmonitor) 172 | 173 | # Dump also secure memory 174 | qmonitor.cmd("pmemsave", {"val": self.secure_mem_base_addr, 175 | "size": self.secure_memory_size, 176 | "filename": self.dump_file_name + "_secure.dump" 177 | }) 178 | 179 | 180 | class ARM_integratorcp(Arm): 181 | def dump_memory(self, qmonitor): 182 | res = qmonitor.cmd("query-memory-size-summary") 183 | memory_size = res["return"]["base-memory"] 184 | 185 | memory_chunks = [(0x0000000000000000, 0x000000000fffffff), 186 | (0x0000000010800000, 0x0000000012ffffff), 187 | (0x0000000013001000, 0x0000000013ffffff), 188 | (0x0000000014800000, 0x0000000014ffffff), 189 | (0x0000000015001000, 0x0000000015ffffff), 190 | (0x0000000016001000, 0x0000000016ffffff), 191 | (0x0000000017001000, 0x0000000017ffffff), 192 | (0x0000000018001000, 0x0000000018ffffff), 193 | (0x0000000019001000, 0x0000000019ffffff), 194 | (0x000000001b000000, 0x000000001bffffff), 195 | (0x000000001c001000, 0x000000001cffffff), 196 | (0x000000001d001000, 0x00000000bfffffff), 197 | (0x00000000c0001000, 0x00000000c7ffffff), 198 | (0x00000000c8000010, 0x00000000c9ffffff), 199 | (0x00000000ca800000, 0x00000000caffffff)] 200 | 201 | dumped_size = 0 202 | i = 0 203 | for i, chunk in enumerate(memory_chunks): 204 | dumped_chunk_size = min(memory_size - dumped_size, chunk[1] - chunk[0] + 1) 205 | qmonitor.cmd("pmemsave", {"val": chunk[0], 206 | "size": dumped_chunk_size, 207 | "filename": self.dump_file_name + ".dump." + str(i) 208 | }) 209 | dumped_size += dumped_chunk_size 210 | 211 | if dumped_size < memory_size: 212 | qmonitor.cmd("pmemsave", {"val": 0xcb800000, 213 | "size": memory_size - dumped_size, 214 | "filename": self.dump_file_name + ".dump." + str(i+1) 215 | }) 216 | 217 | 218 | class ARM_raspi3(Arm): 219 | def dump_memory(self, qmonitor): 220 | res = qmonitor.cmd("query-memory-size-summary") 221 | memory_size = res["return"]["base-memory"] 222 | 223 | memory_chunks = [(0x0, 0x3f002fff), 224 | (0x3f003020, 0x3f006fff), 225 | (0x3f008000, 0x3f00b1ff), 226 | (0x3f00b440, 0x3f00b7ff), 227 | (0x3f00bc00, 0x3f0fffff), 228 | (0x3f101000, 0x3f101fff), 229 | (0x3f103000, 0x3f103fff), 230 | (0x3f104010, 0x3f1fffff), 231 | (0x3f203100, 0x3f203fff), 232 | (0x3f204020, 0x3f204fff), 233 | (0x3f205020, 0x3f20efff), 234 | (0x3f20f080, 0x3f211fff), 235 | (0x3f212008, 0x3f213fff), 236 | (0x3f214100, 0x3f214fff), 237 | (0x3f215100, 0x3f2fffff), 238 | (0x3f300100, 0x3f5fffff), 239 | (0x3f600100, 0x3f803fff), 240 | (0x3f804020, 0x3f804fff), 241 | (0x3f805020, 0x3f8fffff), 242 | (0x3f908000, 0x3f90ffff), 243 | (0x3f918000, 0x3f97ffff), 244 | (0x3f981000, 0x3fdfffff), 245 | (0x3fe00100, 0x3fe04fff), 246 | (0x3fe05100, 0x3fffffff)] 247 | 248 | dumped_size = 0 249 | i = 0 250 | for i, chunk in enumerate(memory_chunks): 251 | dumped_chunk_size = min(memory_size - dumped_size, chunk[1] - chunk[0] + 1) 252 | qmonitor.cmd("pmemsave", {"val": chunk[0], 253 | "size": dumped_chunk_size, 254 | "filename": self.dump_file_name + ".dump." + str(i) 255 | }) 256 | dumped_size += dumped_chunk_size 257 | 258 | if dumped_size < memory_size: 259 | qmonitor.cmd("pmemsave", {"val": 0xcb800000, 260 | "size": memory_size - dumped_size, 261 | "filename": self.dump_file_name + ".dump." + str(i+1) 262 | }) 263 | 264 | 265 | class RISCV(CPU): 266 | pass 267 | 268 | 269 | class MIPS(CPU): 270 | pass 271 | 272 | 273 | class MIPS_malta(MIPS): 274 | def dump_memory(self, qmonitor): 275 | res = qmonitor.cmd("query-memory-size-summary") 276 | memory_size = res["return"]["base-memory"] 277 | 278 | # Dump different chunks of memory 279 | qmonitor.cmd("pmemsave", {"val": 0x0, 280 | "size": min(memory_size, 0x10000000), 281 | "filename": self.dump_file_name + ".dump.0" 282 | }) 283 | if memory_size >= 0x10000000: 284 | qmonitor.cmd("pmemsave", {"val": 0x20000000, 285 | "size": memory_size - 0x10000000, 286 | "filename": self.dump_file_name + ".dump.1" 287 | }) 288 | 289 | 290 | class MIPS_mipssim(MIPS): 291 | def dump_memory(self, qmonitor): 292 | res = qmonitor.cmd("query-memory-size-summary") 293 | memory_size = res["return"]["base-memory"] 294 | 295 | # Dump different chunks of memory 296 | qmonitor.cmd("pmemsave", {"val": 0x0, 297 | "size": min(memory_size, 0x1fc00000), 298 | "filename": self.dump_file_name + ".dump.0" 299 | }) 300 | if memory_size >= 0x1fc00000: 301 | qmonitor.cmd("pmemsave", {"val": 0x20000000, 302 | "size": min(memory_size - 0x1fc00000, 0xe0000000), 303 | "filename": self.dump_file_name + ".dump.1" 304 | }) 305 | 306 | 307 | class POWER(CPU): 308 | def parse_log_row(self, data_log, start_time): 309 | if self.debug: 310 | print(data_log) 311 | 312 | time_now = (datetime.now() - start_time).total_seconds() 313 | keys_values = data_log.split("|") 314 | reg_name, reg_value = keys_values[0].split("=") 315 | reg_value = int(reg_value.strip(), 16) 316 | 317 | if reg_name == "SDR1": 318 | _, vsid = keys_values[1].split("=") 319 | if reg_value not in self.regs[reg_name]: 320 | if vsid.strip() == "-1": 321 | self.regs[reg_name][reg_value] = {"first_seen": time_now, "last_seen": time_now, "vsids": {}} 322 | else: 323 | vsid = int(vsid.strip(), 16) 324 | self.regs[reg_name][reg_value] = {"first_seen": time_now, "last_seen": time_now, 325 | "vsids": {vsid: (time_now, time_now)}} 326 | else: 327 | self.regs[reg_name][reg_value]["last_seen"] = time_now 328 | if vsid.strip() != "-1": 329 | vsid = int(vsid.strip(), 16) 330 | if vsid not in self.regs[reg_name][reg_value]["vsids"]: 331 | self.regs[reg_name][reg_value]["vsids"][vsid] = (time_now, time_now) 332 | else: 333 | first_seen = self.regs[reg_name][reg_value]["vsids"][vsid][0] 334 | self.regs[reg_name][reg_value]["vsids"][vsid] = (first_seen, time_now) 335 | pass 336 | 337 | else: 338 | if reg_value not in self.regs[reg_name]: 339 | self.regs[reg_name][reg_value] = (time_now, time_now) 340 | else: 341 | first_seen = self.regs[reg_name][reg_value][0] 342 | self.regs[reg_name][reg_value] = (first_seen, time_now) 343 | 344 | 345 | parser = argparse.ArgumentParser(description='You have to call QEMU with "-qmp tcp:HOST:PORT,server,' 346 | 'nowait -d fossil -D pipe_file" options and WITHOUT "-enable-kvm" option') 347 | parser.add_argument("pipe_file", help="PIPE for QEMU log file", type=str) 348 | parser.add_argument("qmp", help="QEMU QMP channel (host:port)", type=str) 349 | parser.add_argument("prefix_filename", help="Prefix for dump and .regs file.", type=str) 350 | parser.add_argument("--debug", help="Print debug info", action="store_true", default=False) 351 | parser.add_argument("--timer", help="Shutdown machine after N seconds", type=int, default=0) 352 | subparser = parser.add_subparsers(required=True, help="Architectures", dest="arch") 353 | parser_intel = subparser.add_parser("intel") 354 | parser_intelq35 = subparser.add_parser("intel_q35") 355 | parser_ppc = subparser.add_parser("ppc") 356 | parser_arm_virt = subparser.add_parser("arm_virt") 357 | parser_arm_virt_secure = subparser.add_parser("arm_virt_secure") 358 | parser_arm_vexpress_a9 = subparser.add_parser("arm_vexpress_a9") 359 | parser_arm_vexpress_a15 = subparser.add_parser("arm_vexpress_a15") 360 | parser_arm_integratorcp = subparser.add_parser("arm_integratorcp") 361 | parser_arm_raspi3 = subparser.add_parser("arm_raspi3") 362 | parser_riscv = subparser.add_parser("riscv") 363 | parser_mips = subparser.add_parser("mips") 364 | parser_mips_malta = subparser.add_parser("mips_malta") 365 | parser_mips_mipssim = subparser.add_parser("mips_mipssim") 366 | parser_power = subparser.add_parser("power") 367 | 368 | args = parser.parse_args() 369 | 370 | try: 371 | qemu_qmp = args.qmp.split(":") 372 | qemu_qmp[1] = int(qemu_qmp[1]) 373 | qemu_qmp = tuple(qemu_qmp) 374 | except Exception as e: 375 | parser.error("Invalid QMP channel format!") 376 | exit(1) 377 | 378 | 379 | try: 380 | regs_file = open(args.prefix_filename + ".regs", "wb") 381 | except Exception as e: 382 | print(e) 383 | print("Unable to open output file!") 384 | exit(1) 385 | 386 | if args.arch == "intel": 387 | log_class = Intel(0, args.prefix_filename, args.debug) 388 | elif args.arch == "intel_q35": 389 | log_class = IntelQ35(0, args.prefix_filename, args.debug) 390 | 391 | elif args.arch == "ppc": 392 | log_class = PPC(0, args.prefix_filename, args.debug) 393 | 394 | elif args.arch == "arm_virt": 395 | log_class = Arm(0x40000000, args.prefix_filename, args.debug) 396 | elif args.arch == "arm_virt_secure": 397 | log_class = ArmVirtSecure(0x40000000, args.prefix_filename, args.debug) 398 | elif args.arch == "arm_vexpress_a9": 399 | log_class = Arm(0x60000000, args.prefix_filename, args.debug) 400 | elif args.arch == "arm_vexpress_a15": 401 | log_class = Arm(0x80000000, args.prefix_filename, args.debug) 402 | elif args.arch == "arm_integratorcp": 403 | log_class = ARM_integratorcp(0x00000000, args.prefix_filename, args.debug) 404 | elif args.arch == "arm_raspi3": 405 | log_class = ARM_raspi3(0x00000000, args.prefix_filename, args.debug) 406 | 407 | elif args.arch == "riscv": 408 | log_class = RISCV(0x80000000, args.prefix_filename, args.debug) 409 | 410 | elif args.arch == "mips": 411 | log_class = MIPS(0, args.prefix_filename, args.debug) 412 | elif args.arch == "mips_malta": 413 | log_class = MIPS_malta(0, args.prefix_filename, args.debug) 414 | elif args.arch == "mips_mipssim": 415 | log_class = MIPS_mipssim(0, args.prefix_filename, args.debug) 416 | 417 | elif args.arch == "power": 418 | log_class = POWER(0, args.prefix_filename, args.debug) 419 | 420 | else: 421 | print("Invalid architecture") 422 | exit(1) 423 | 424 | qemu_fifo = args.pipe_file 425 | qemu_monitor = None 426 | fifo = None 427 | 428 | 429 | def ctrl_c_handler(sig, frame): 430 | uptime = (datetime.now() - start_time_g).total_seconds() 431 | print("\n\nDump the memory, save registers and shutdown the machine") 432 | 433 | # Dump the memory 434 | log_class.dump_memory(qemu_monitor) 435 | 436 | # Save data 437 | log_class.regs["uptime"] = uptime 438 | pickle.dump(log_class.regs, regs_file) 439 | qemu_monitor.close() 440 | regs_file.close() 441 | 442 | print("Done!") 443 | exit(0) 444 | 445 | 446 | def timer_handler(): 447 | os.kill(os.getpid(), SIGINT) 448 | 449 | 450 | # Open the QEMU log FIFO 451 | try: 452 | os.mkfifo(qemu_fifo) 453 | except OSError as oe: 454 | if oe.errno != errno.EEXIST: 455 | print(oe) 456 | exit(1) 457 | 458 | 459 | print("Waiting for QEMU logs in FIFO...") 460 | already_receive = False 461 | fifo = open(qemu_fifo) 462 | start_time_g = datetime.now() 463 | 464 | for data in fifo: 465 | if not already_receive: 466 | print("QEMU connected!") 467 | already_receive = True 468 | 469 | # Try to open the QMP channel 470 | try: 471 | qemu_monitor = QEMUMonitorProtocol(qemu_qmp) 472 | qemu_monitor.connect() 473 | except Exception as e: 474 | regs_file.close() 475 | print(e) 476 | print("Impossible to connect to QEMU QMP channel!") 477 | exit(1) 478 | print("QEMU QMP connected!") 479 | 480 | signal(SIGINT, ctrl_c_handler) 481 | if args.timer > 0: 482 | t = Timer(args.timer, timer_handler) 483 | t.start() 484 | print("After {} seconds it dump the memory, save the registers, " 485 | "and shutdown the machine. So wait...".format(str(args.timer))) 486 | else: 487 | print("Press CTRL-C to dump the memory, save the registers, and shutdown the machine") 488 | 489 | log_class.parse_log_row(data, start_time_g) 490 | -------------------------------------------------------------------------------- /qemu/qemu_v5.0.0.patch: -------------------------------------------------------------------------------- 1 | diff --git a/.gitignore b/.gitignore 2 | index 0c5af83aa7..7d90a9808e 100644 3 | --- a/.gitignore 4 | +++ b/.gitignore 5 | @@ -1,3 +1,4 @@ 6 | +build/ 7 | /.doctrees 8 | /config-devices.* 9 | /config-all-devices.* 10 | diff --git a/hw/ppc/pnv.c b/hw/ppc/pnv.c 11 | index c9cb6fa357..8271bc046d 100644 12 | --- a/hw/ppc/pnv.c 13 | +++ b/hw/ppc/pnv.c 14 | @@ -1749,6 +1749,10 @@ static void pnv_chip_core_realize(PnvChip *chip, Error **errp) 15 | "pir", &error_fatal); 16 | object_property_set_int(OBJECT(pnv_core), pnv->fw_load_addr, 17 | "hrmor", &error_fatal); 18 | + 19 | + qemu_log_mask(CPU_LOG_MMUSHELL, "HRMOR=%016" PRIx64 "\n", pnv->fw_load_addr); 20 | + 21 | + 22 | object_property_set_link(OBJECT(pnv_core), OBJECT(chip), "chip", 23 | &error_abort); 24 | object_property_set_bool(OBJECT(pnv_core), true, "realized", 25 | diff --git a/hw/ppc/pnv_core.c b/hw/ppc/pnv_core.c 26 | index 234562040d..3b88b68c4c 100644 27 | --- a/hw/ppc/pnv_core.c 28 | +++ b/hw/ppc/pnv_core.c 29 | @@ -57,6 +57,7 @@ static void pnv_core_cpu_reset(PnvCore *pc, PowerPCCPU *cpu) 30 | env->msr |= MSR_HVB; /* Hypervisor mode */ 31 | 32 | env->spr[SPR_HRMOR] = pc->hrmor; 33 | + qemu_log_mask(CPU_LOG_MMUSHELL, "HRMOR=%016" PRIx64 "\n", env->spr[SPR_HRMOR]); 34 | 35 | pcc->intc_reset(pc->chip, cpu); 36 | } 37 | diff --git a/include/qemu/log.h b/include/qemu/log.h 38 | index f4724f7330..f1ef63d7ed 100644 39 | --- a/include/qemu/log.h 40 | +++ b/include/qemu/log.h 41 | @@ -64,6 +64,7 @@ static inline bool qemu_log_separate(void) 42 | #define CPU_LOG_PLUGIN (1 << 18) 43 | /* LOG_STRACE is used for user-mode strace logging. */ 44 | #define LOG_STRACE (1 << 19) 45 | +#define CPU_LOG_MMUSHELL (1 << 30) 46 | 47 | /* Lock output for a series of related logs. Since this is not needed 48 | * for a single qemu_log / qemu_log_mask / qemu_log_mask_and_addr, we 49 | diff --git a/target/arm/helper.c b/target/arm/helper.c 50 | index 7e9ea5d20f..35aa42e082 100644 51 | --- a/target/arm/helper.c 52 | +++ b/target/arm/helper.c 53 | @@ -164,6 +164,27 @@ static void raw_write(CPUARMState *env, const ARMCPRegInfo *ri, 54 | } 55 | } 56 | 57 | +static void raw_write_tcr_el(CPUARMState *env, const ARMCPRegInfo *ri, 58 | + uint64_t value) 59 | +{ 60 | + raw_write(env, ri, value); 61 | + qemu_log_mask(CPU_LOG_MMUSHELL, "%s=%16" PRIx64 "\n", ri->name, value); 62 | +} 63 | + 64 | +static void raw_write_contextidr_el(CPUARMState *env, const ARMCPRegInfo *ri, 65 | + uint64_t value) 66 | +{ 67 | + raw_write(env, ri, value); 68 | + qemu_log_mask(CPU_LOG_MMUSHELL, "%s=%16" PRIx64 "\n", ri->name, value); 69 | +} 70 | + 71 | +static void raw_write_sctlr(CPUARMState *env, const ARMCPRegInfo *ri, 72 | + uint64_t value) 73 | +{ 74 | + raw_write(env, ri, value); 75 | + qemu_log_mask(CPU_LOG_MMUSHELL, "%s=%16" PRIx64 "\n", ri->name, value); 76 | +} 77 | + 78 | static void *raw_ptr(CPUARMState *env, const ARMCPRegInfo *ri) 79 | { 80 | return (char *)env + ri->fieldoffset; 81 | @@ -706,6 +727,8 @@ static void contextidr_write(CPUARMState *env, const ARMCPRegInfo *ri, 82 | tlb_flush(CPU(cpu)); 83 | } 84 | raw_write(env, ri, value); 85 | + 86 | + qemu_log_mask(CPU_LOG_MMUSHELL, "%s=%16" PRIx64 "\n", ri->name, value); 87 | } 88 | 89 | /* IS variants of TLB operations must affect all cores */ 90 | @@ -929,7 +952,7 @@ static const ARMCPRegInfo cp_reginfo[] = { 91 | .access = PL1_RW, .accessfn = access_tvm_trvm, 92 | .secure = ARM_CP_SECSTATE_NS, 93 | .fieldoffset = offsetof(CPUARMState, cp15.contextidr_el[1]), 94 | - .resetvalue = 0, .writefn = contextidr_write, .raw_writefn = raw_write, }, 95 | + .resetvalue = 0, .writefn = contextidr_write, .raw_writefn = raw_write_contextidr_el, }, 96 | { .name = "CONTEXTIDR_S", .state = ARM_CP_STATE_AA32, 97 | .cp = 15, .opc1 = 0, .crn = 13, .crm = 0, .opc2 = 1, 98 | .access = PL1_RW, .accessfn = access_tvm_trvm, 99 | @@ -2056,6 +2079,8 @@ static void vbar_write(CPUARMState *env, const ARMCPRegInfo *ri, 100 | * requires the bottom five bits to be RAZ/WI because they're UNK/SBZP.) 101 | */ 102 | raw_write(env, ri, value & ~0x1FULL); 103 | + 104 | + qemu_log_mask(CPU_LOG_MMUSHELL, "%s=%16" PRIx64 "\n", ri->name, value); 105 | } 106 | 107 | static void scr_write(CPUARMState *env, const ARMCPRegInfo *ri, uint64_t value) 108 | @@ -3951,6 +3976,8 @@ static void vmsa_ttbcr_raw_write(CPUARMState *env, const ARMCPRegInfo *ri, 109 | tcr->raw_tcr = value; 110 | tcr->mask = ~(((uint32_t)0xffffffffu) >> maskshift); 111 | tcr->base_mask = ~((uint32_t)0x3fffu >> maskshift); 112 | + 113 | + qemu_log_mask(CPU_LOG_MMUSHELL, "%s=%16" PRIx64 "\n", ri->name, value); 114 | } 115 | 116 | static void vmsa_ttbcr_write(CPUARMState *env, const ARMCPRegInfo *ri, 117 | @@ -3968,6 +3995,8 @@ static void vmsa_ttbcr_write(CPUARMState *env, const ARMCPRegInfo *ri, 118 | /* Preserve the high half of TCR_EL1, set via TTBCR2. */ 119 | value = deposit64(tcr->raw_tcr, 0, 32, value); 120 | vmsa_ttbcr_raw_write(env, ri, value); 121 | + 122 | + qemu_log_mask(CPU_LOG_MMUSHELL, "%s=%16" PRIx64 "\n", ri->name, value); 123 | } 124 | 125 | static void vmsa_ttbcr_reset(CPUARMState *env, const ARMCPRegInfo *ri) 126 | @@ -3980,6 +4009,8 @@ static void vmsa_ttbcr_reset(CPUARMState *env, const ARMCPRegInfo *ri) 127 | tcr->raw_tcr = 0; 128 | tcr->mask = 0; 129 | tcr->base_mask = 0xffffc000u; 130 | + 131 | + qemu_log_mask(CPU_LOG_MMUSHELL, "%s=%16" PRIx64 "\n", ri->name, tcr->raw_tcr); 132 | } 133 | 134 | static void vmsa_tcr_el12_write(CPUARMState *env, const ARMCPRegInfo *ri, 135 | @@ -3991,6 +4022,8 @@ static void vmsa_tcr_el12_write(CPUARMState *env, const ARMCPRegInfo *ri, 136 | /* For AArch64 the A1 bit could result in a change of ASID, so TLB flush. */ 137 | tlb_flush(CPU(cpu)); 138 | tcr->raw_tcr = value; 139 | + 140 | + qemu_log_mask(CPU_LOG_MMUSHELL, "%s=%16" PRIx64 "\n", ri->name, value); 141 | } 142 | 143 | static void vmsa_ttbr_write(CPUARMState *env, const ARMCPRegInfo *ri, 144 | @@ -4003,6 +4036,8 @@ static void vmsa_ttbr_write(CPUARMState *env, const ARMCPRegInfo *ri, 145 | tlb_flush(CPU(cpu)); 146 | } 147 | raw_write(env, ri, value); 148 | + 149 | + qemu_log_mask(CPU_LOG_MMUSHELL, "%s=%16" PRIx64 "\n", ri->name, value); 150 | } 151 | 152 | static void vmsa_tcr_ttbr_el2_write(CPUARMState *env, const ARMCPRegInfo *ri, 153 | @@ -4022,6 +4057,8 @@ static void vmsa_tcr_ttbr_el2_write(CPUARMState *env, const ARMCPRegInfo *ri, 154 | ARMMMUIdxBit_E20_0); 155 | } 156 | raw_write(env, ri, value); 157 | + 158 | + qemu_log_mask(CPU_LOG_MMUSHELL, "%s=%16" PRIx64 "\n", ri->name, value); 159 | } 160 | 161 | static void vttbr_write(CPUARMState *env, const ARMCPRegInfo *ri, 162 | @@ -4042,6 +4079,8 @@ static void vttbr_write(CPUARMState *env, const ARMCPRegInfo *ri, 163 | ARMMMUIdxBit_Stage2); 164 | raw_write(env, ri, value); 165 | } 166 | + 167 | + qemu_log_mask(CPU_LOG_MMUSHELL, "%s=%16" PRIx64 "\n", ri->name, value); 168 | } 169 | 170 | static const ARMCPRegInfo vmsa_pmsa_cp_reginfo[] = { 171 | @@ -4086,7 +4125,7 @@ static const ARMCPRegInfo vmsa_cp_reginfo[] = { 172 | .opc0 = 3, .crn = 2, .crm = 0, .opc1 = 0, .opc2 = 2, 173 | .access = PL1_RW, .accessfn = access_tvm_trvm, 174 | .writefn = vmsa_tcr_el12_write, 175 | - .resetfn = vmsa_ttbcr_reset, .raw_writefn = raw_write, 176 | + .resetfn = vmsa_ttbcr_reset, .raw_writefn = raw_write_tcr_el, 177 | .fieldoffset = offsetof(CPUARMState, cp15.tcr_el[1]) }, 178 | { .name = "TTBCR", .cp = 15, .crn = 2, .crm = 0, .opc1 = 0, .opc2 = 2, 179 | .access = PL1_RW, .accessfn = access_tvm_trvm, 180 | @@ -4803,6 +4842,8 @@ static void sctlr_write(CPUARMState *env, const ARMCPRegInfo *ri, 181 | */ 182 | arm_rebuild_hflags(env); 183 | } 184 | + 185 | + qemu_log_mask(CPU_LOG_MMUSHELL, "%s=%16" PRIx64 "\n", ri->name, value); 186 | } 187 | 188 | static CPAccessResult fpexc32_access(CPUARMState *env, const ARMCPRegInfo *ri, 189 | @@ -5577,7 +5618,7 @@ static const ARMCPRegInfo el2_cp_reginfo[] = { 190 | .fieldoffset = offsetof(CPUARMState, cp15.vttbr_el2) }, 191 | { .name = "SCTLR_EL2", .state = ARM_CP_STATE_BOTH, 192 | .opc0 = 3, .opc1 = 4, .crn = 1, .crm = 0, .opc2 = 0, 193 | - .access = PL2_RW, .raw_writefn = raw_write, .writefn = sctlr_write, 194 | + .access = PL2_RW, .raw_writefn = raw_write_sctlr, .writefn = sctlr_write, 195 | .fieldoffset = offsetof(CPUARMState, cp15.sctlr_el[2]) }, 196 | { .name = "TPIDR_EL2", .state = ARM_CP_STATE_BOTH, 197 | .opc0 = 3, .opc1 = 4, .crn = 13, .crm = 0, .opc2 = 2, 198 | @@ -7635,7 +7676,7 @@ void register_cp_regs_for_features(ARMCPU *cpu) 199 | { .name = "SCTLR_EL3", .state = ARM_CP_STATE_AA64, 200 | .opc0 = 3, .opc1 = 6, .crn = 1, .crm = 0, .opc2 = 0, 201 | .access = PL3_RW, 202 | - .raw_writefn = raw_write, .writefn = sctlr_write, 203 | + .raw_writefn = raw_write_sctlr, .writefn = sctlr_write, 204 | .fieldoffset = offsetof(CPUARMState, cp15.sctlr_el[3]), 205 | .resetvalue = cpu->reset_sctlr }, 206 | REGINFO_SENTINEL 207 | @@ -7985,7 +8026,7 @@ void register_cp_regs_for_features(ARMCPU *cpu) 208 | .bank_fieldoffsets = { offsetof(CPUARMState, cp15.sctlr_s), 209 | offsetof(CPUARMState, cp15.sctlr_ns) }, 210 | .writefn = sctlr_write, .resetvalue = cpu->reset_sctlr, 211 | - .raw_writefn = raw_write, 212 | + .raw_writefn = raw_write_sctlr, 213 | }; 214 | if (arm_feature(env, ARM_FEATURE_XSCALE)) { 215 | /* Normally we would always end the TB on an SCTLR write, but Linux 216 | diff --git a/target/i386/helper.c b/target/i386/helper.c 217 | index c3a6e4fabe..a50e046b29 100644 218 | --- a/target/i386/helper.c 219 | +++ b/target/i386/helper.c 220 | @@ -492,6 +492,10 @@ void x86_cpu_dump_state(CPUState *cs, FILE *f, int flags) 221 | env->gdt.base, env->gdt.limit); 222 | qemu_fprintf(f, "IDT= %016" PRIx64 " %08x\n", 223 | env->idt.base, env->idt.limit); 224 | + 225 | + qemu_log_mask(CPU_LOG_MMUSHELL, "GDTR=%016" PRIx64 "|limit=%08x\n", env->gdt.base, env->gdt.limit); 226 | + qemu_log_mask(CPU_LOG_MMUSHELL, "IDTR=%016" PRIx64 "|limit=%08x\n", env->idt.base, env->idt.limit); 227 | + 228 | qemu_fprintf(f, "CR0=%08x CR2=%016" PRIx64 " CR3=%016" PRIx64 " CR4=%08x\n", 229 | (uint32_t)env->cr[0], 230 | env->cr[2], 231 | @@ -508,6 +512,10 @@ void x86_cpu_dump_state(CPUState *cs, FILE *f, int flags) 232 | (uint32_t)env->gdt.base, env->gdt.limit); 233 | qemu_fprintf(f, "IDT= %08x %08x\n", 234 | (uint32_t)env->idt.base, env->idt.limit); 235 | + 236 | + qemu_log_mask(CPU_LOG_MMUSHELL, "GDTR=%08" PRIx32 "|limit=%08x\n", (uint32_t)env->gdt.base, env->gdt.limit); 237 | + qemu_log_mask(CPU_LOG_MMUSHELL, "IDTR=%08" PRIx32 "|limit=%08x\n", (uint32_t)env->idt.base, env->idt.limit); 238 | + 239 | qemu_fprintf(f, "CR0=%08x CR2=%08x CR3=%08x CR4=%08x\n", 240 | (uint32_t)env->cr[0], 241 | (uint32_t)env->cr[2], 242 | @@ -669,7 +677,10 @@ void cpu_x86_update_cr3(CPUX86State *env, target_ulong new_cr3) 243 | if (env->cr[0] & CR0_PG_MASK) { 244 | qemu_log_mask(CPU_LOG_MMU, 245 | "CR3 update: CR3=" TARGET_FMT_lx "\n", new_cr3); 246 | - tlb_flush(env_cpu(env)); 247 | + 248 | + qemu_log_mask(CPU_LOG_MMUSHELL,"CR3=" TARGET_FMT_lx "\n", new_cr3); 249 | + 250 | + tlb_flush(env_cpu(env)); 251 | } 252 | } 253 | 254 | diff --git a/target/i386/seg_helper.c b/target/i386/seg_helper.c 255 | index b96de068ca..fae9b20448 100644 256 | --- a/target/i386/seg_helper.c 257 | +++ b/target/i386/seg_helper.c 258 | @@ -1208,6 +1208,15 @@ static void do_interrupt_all(X86CPU *cpu, int intno, int is_int, 259 | { 260 | CPUX86State *env = &cpu->env; 261 | 262 | + #ifdef TARGET_X86_64 263 | + qemu_log_mask(CPU_LOG_MMUSHELL, "GDTR=%016" PRIx64 "|limit=%08x\n", env->gdt.base, env->gdt.limit); 264 | + qemu_log_mask(CPU_LOG_MMUSHELL, "IDTR=%016" PRIx64 "|limit=%08x\n", env->idt.base, env->idt.limit); 265 | + #else 266 | + qemu_log_mask(CPU_LOG_MMUSHELL, "GDTR=%08" PRIx32 "|limit=%08x\n", env->gdt.base, env->gdt.limit); 267 | + qemu_log_mask(CPU_LOG_MMUSHELL, "IDTR=%08" PRIx32 "|limit=%08x\n", env->idt.base, env->idt.limit); 268 | + #endif 269 | + 270 | + 271 | if (qemu_loglevel_mask(CPU_LOG_INT)) { 272 | if ((env->cr[0] & CR0_PE_MASK)) { 273 | static int count; 274 | diff --git a/target/i386/smm_helper.c b/target/i386/smm_helper.c 275 | index eb5aa6eb3d..3b6cd3e3f6 100644 276 | --- a/target/i386/smm_helper.c 277 | +++ b/target/i386/smm_helper.c 278 | @@ -54,6 +54,14 @@ void do_smm_enter(X86CPU *cpu) 279 | qemu_log_mask(CPU_LOG_INT, "SMM: enter\n"); 280 | log_cpu_state_mask(CPU_LOG_INT, CPU(cpu), CPU_DUMP_CCOP); 281 | 282 | + #ifdef TARGET_X86_64 283 | + qemu_log_mask(CPU_LOG_MMUSHELL, "GDT=%016" PRIx64 "|limit=%08x\n", env->gdt.base, env->gdt.limit); 284 | + qemu_log_mask(CPU_LOG_MMUSHELL, "IDT=%016" PRIx64 "|limit=%08x\n", env->idt.base, env->idt.limit); 285 | + #else 286 | + qemu_log_mask(CPU_LOG_MMUSHELL, "GDT=%08" PRIx32 "|limit=%08x\n", env->gdt.base, env->gdt.limit); 287 | + qemu_log_mask(CPU_LOG_MMUSHELL, "IDT=%08" PRIx32 "|limit=%08x\n", env->idt.base, env->idt.limit); 288 | + #endif 289 | + 290 | env->msr_smi_count++; 291 | env->hflags |= HF_SMM_MASK; 292 | if (env->hflags2 & HF2_NMI_MASK) { 293 | @@ -327,6 +335,14 @@ void helper_rsm(CPUX86State *env) 294 | 295 | qemu_log_mask(CPU_LOG_INT, "SMM: after RSM\n"); 296 | log_cpu_state_mask(CPU_LOG_INT, CPU(cpu), CPU_DUMP_CCOP); 297 | + #ifdef TARGET_X86_64 298 | + qemu_log_mask(CPU_LOG_MMUSHELL, "GDTR=%016" PRIx64 "|limit=%08x\n", env->gdt.base, env->gdt.limit); 299 | + qemu_log_mask(CPU_LOG_MMUSHELL, "IDTR=%016" PRIx64 "|limit=%08x\n", env->idt.base, env->idt.limit); 300 | + #else 301 | + qemu_log_mask(CPU_LOG_MMUSHELL, "GDTR=%08" PRIx32 "|limit=%08x\n", env->gdt.base, env->gdt.limit); 302 | + qemu_log_mask(CPU_LOG_MMUSHELL, "IDTR=%08" PRIx32 "|limit=%08x\n", env->idt.base, env->idt.limit); 303 | + #endif 304 | + 305 | } 306 | 307 | #endif /* !CONFIG_USER_ONLY */ 308 | diff --git a/target/mips/cp0_helper.c b/target/mips/cp0_helper.c 309 | index bbf12e4a97..f727fd15e8 100644 310 | --- a/target/mips/cp0_helper.c 311 | +++ b/target/mips/cp0_helper.c 312 | @@ -859,6 +859,7 @@ void helper_dmtc0_entrylo1(CPUMIPSState *env, uint64_t arg1) 313 | void helper_mtc0_context(CPUMIPSState *env, target_ulong arg1) 314 | { 315 | env->CP0_Context = (env->CP0_Context & 0x007FFFFF) | (arg1 & ~0x007FFFFF); 316 | + qemu_log_mask(CPU_LOG_MMUSHELL, "Context=" TARGET_FMT_lx "\n", env->CP0_Context); 317 | } 318 | 319 | void helper_mtc0_memorymapid(CPUMIPSState *env, target_ulong arg1) 320 | @@ -880,12 +881,26 @@ void update_pagemask(CPUMIPSState *env, target_ulong arg1, int32_t *pagemask) 321 | mask == 0x003F || mask == 0x00FF || mask == 0x03FF || 322 | mask == 0x0FFF || mask == 0x3FFF || mask == 0xFFFF)) { 323 | env->CP0_PageMask = arg1 & (0x1FFFFFFF & (TARGET_PAGE_MASK << 1)); 324 | + 325 | + #if defined(TARGET_MIPS64) 326 | + qemu_log_mask(CPU_LOG_MMUSHELL, "PageMask=" TARGET_FMT_lx "\n", (unsigned long int)env->CP0_PageMask); 327 | + #else 328 | + qemu_log_mask(CPU_LOG_MMUSHELL, "PageMask=" TARGET_FMT_lx "\n", (unsigned int)env->CP0_PageMask); 329 | + #endif 330 | + 331 | } 332 | } 333 | 334 | void helper_mtc0_pagemask(CPUMIPSState *env, target_ulong arg1) 335 | { 336 | update_pagemask(env, arg1, &env->CP0_PageMask); 337 | + 338 | + #if defined(TARGET_MIPS64) 339 | + qemu_log_mask(CPU_LOG_MMUSHELL, "PageMask=" TARGET_FMT_lx "\n", (unsigned long int)env->CP0_PageMask); 340 | + #else 341 | + qemu_log_mask(CPU_LOG_MMUSHELL, "PageMask=" TARGET_FMT_lx "\n", (unsigned int)env->CP0_PageMask); 342 | + #endif 343 | + 344 | } 345 | 346 | void helper_mtc0_pagegrain(CPUMIPSState *env, target_ulong arg1) 347 | @@ -896,6 +911,13 @@ void helper_mtc0_pagegrain(CPUMIPSState *env, target_ulong arg1) 348 | (env->CP0_PageGrain & ~env->CP0_PageGrain_rw_bitmask); 349 | compute_hflags(env); 350 | restore_pamask(env); 351 | + 352 | + #if defined(TARGET_MIPS64) 353 | + qemu_log_mask(CPU_LOG_MMUSHELL, "PageGrain=" TARGET_FMT_lx "\n", (unsigned long int)env->CP0_PageGrain); 354 | + #else 355 | + qemu_log_mask(CPU_LOG_MMUSHELL, "PageGrain=" TARGET_FMT_lx "\n", (unsigned int)env->CP0_PageGrain); 356 | + #endif 357 | + 358 | } 359 | 360 | void helper_mtc0_segctl0(CPUMIPSState *env, target_ulong arg1) 361 | @@ -904,6 +926,7 @@ void helper_mtc0_segctl0(CPUMIPSState *env, target_ulong arg1) 362 | 363 | env->CP0_SegCtl0 = arg1 & CP0SC0_MASK; 364 | tlb_flush(cs); 365 | + qemu_log_mask(CPU_LOG_MMUSHELL, "SegCtl0=" TARGET_FMT_lx "\n", env->CP0_SegCtl0); 366 | } 367 | 368 | void helper_mtc0_segctl1(CPUMIPSState *env, target_ulong arg1) 369 | @@ -912,6 +935,7 @@ void helper_mtc0_segctl1(CPUMIPSState *env, target_ulong arg1) 370 | 371 | env->CP0_SegCtl1 = arg1 & CP0SC1_MASK; 372 | tlb_flush(cs); 373 | + qemu_log_mask(CPU_LOG_MMUSHELL, "SegCtl1=" TARGET_FMT_lx "\n", env->CP0_SegCtl1); 374 | } 375 | 376 | void helper_mtc0_segctl2(CPUMIPSState *env, target_ulong arg1) 377 | @@ -920,6 +944,7 @@ void helper_mtc0_segctl2(CPUMIPSState *env, target_ulong arg1) 378 | 379 | env->CP0_SegCtl2 = arg1 & CP0SC2_MASK; 380 | tlb_flush(cs); 381 | + qemu_log_mask(CPU_LOG_MMUSHELL, "SegCtl2=" TARGET_FMT_lx "\n", env->CP0_SegCtl2); 382 | } 383 | 384 | void helper_mtc0_pwfield(CPUMIPSState *env, target_ulong arg1) 385 | @@ -982,6 +1007,8 @@ void helper_mtc0_pwfield(CPUMIPSState *env, target_ulong arg1) 386 | (old_ptew << CP0PF_PTEW); 387 | } 388 | #endif 389 | + 390 | +qemu_log_mask(CPU_LOG_MMUSHELL, "PWField=" TARGET_FMT_lx "\n", env->CP0_PWField); 391 | } 392 | 393 | void helper_mtc0_pwsize(CPUMIPSState *env, target_ulong arg1) 394 | @@ -991,6 +1018,7 @@ void helper_mtc0_pwsize(CPUMIPSState *env, target_ulong arg1) 395 | #else 396 | env->CP0_PWSize = arg1 & 0x3FFFFFFF; 397 | #endif 398 | +qemu_log_mask(CPU_LOG_MMUSHELL, "PWSize=" TARGET_FMT_lx "\n", env->CP0_PWSize); 399 | } 400 | 401 | void helper_mtc0_wired(CPUMIPSState *env, target_ulong arg1) 402 | @@ -1002,6 +1030,12 @@ void helper_mtc0_wired(CPUMIPSState *env, target_ulong arg1) 403 | } else { 404 | env->CP0_Wired = arg1 % env->tlb->nb_tlb; 405 | } 406 | + 407 | + #if defined(TARGET_MIPS64) 408 | + qemu_log_mask(CPU_LOG_MMUSHELL, "Wired=" TARGET_FMT_lx "\n", (unsigned long int)env->CP0_Wired); 409 | + #else 410 | + qemu_log_mask(CPU_LOG_MMUSHELL, "Wired=" TARGET_FMT_lx "\n", (unsigned int)env->CP0_Wired); 411 | + #endif 412 | } 413 | 414 | void helper_mtc0_pwctl(CPUMIPSState *env, target_ulong arg1) 415 | @@ -1009,8 +1043,10 @@ void helper_mtc0_pwctl(CPUMIPSState *env, target_ulong arg1) 416 | #if defined(TARGET_MIPS64) 417 | /* PWEn = 0. Hardware page table walking is not implemented. */ 418 | env->CP0_PWCtl = (env->CP0_PWCtl & 0x000000C0) | (arg1 & 0x5C00003F); 419 | + qemu_log_mask(CPU_LOG_MMUSHELL, "PWCtl=" TARGET_FMT_lx "\n", (unsigned long int)env->CP0_PWCtl); 420 | #else 421 | env->CP0_PWCtl = (arg1 & 0x800000FF); 422 | + qemu_log_mask(CPU_LOG_MMUSHELL, "PWCtl=" TARGET_FMT_lx "\n", (unsigned int)env->CP0_PWCtl); 423 | #endif 424 | } 425 | 426 | @@ -1247,6 +1283,12 @@ void helper_mtc0_ebase(CPUMIPSState *env, target_ulong arg1) 427 | mask |= ~0x3FFFFFFF; 428 | } 429 | env->CP0_EBase = (env->CP0_EBase & ~mask) | (arg1 & mask); 430 | + 431 | + #if defined(TARGET_MIPS64) 432 | + qemu_log_mask(CPU_LOG_MMUSHELL, "EBase=" TARGET_FMT_lx "\n", (unsigned long int)env->CP0_EBase); 433 | + #else 434 | + qemu_log_mask(CPU_LOG_MMUSHELL, "EBase=" TARGET_FMT_lx "\n", (unsigned int)env->CP0_EBase); 435 | + #endif 436 | } 437 | 438 | void helper_mttc0_ebase(CPUMIPSState *env, target_ulong arg1) 439 | @@ -1282,6 +1324,12 @@ target_ulong helper_mftc0_configx(CPUMIPSState *env, target_ulong idx) 440 | void helper_mtc0_config0(CPUMIPSState *env, target_ulong arg1) 441 | { 442 | env->CP0_Config0 = (env->CP0_Config0 & 0x81FFFFF8) | (arg1 & 0x00000007); 443 | + 444 | + #if defined(TARGET_MIPS64) 445 | + qemu_log_mask(CPU_LOG_MMUSHELL, "Config=" TARGET_FMT_lx "\n", (unsigned long int)env->CP0_Config0); 446 | + #else 447 | + qemu_log_mask(CPU_LOG_MMUSHELL, "Config=" TARGET_FMT_lx "\n", env->CP0_Config0); 448 | + #endif 449 | } 450 | 451 | void helper_mtc0_config2(CPUMIPSState *env, target_ulong arg1) 452 | @@ -1302,6 +1350,12 @@ void helper_mtc0_config4(CPUMIPSState *env, target_ulong arg1) 453 | { 454 | env->CP0_Config4 = (env->CP0_Config4 & (~env->CP0_Config4_rw_bitmask)) | 455 | (arg1 & env->CP0_Config4_rw_bitmask); 456 | + 457 | + #if defined(TARGET_MIPS64) 458 | + qemu_log_mask(CPU_LOG_MMUSHELL, "Config4=" TARGET_FMT_lx "\n", (unsigned long int)env->CP0_Config4); 459 | + #else 460 | + qemu_log_mask(CPU_LOG_MMUSHELL, "Config4=" TARGET_FMT_lx "\n", env->CP0_Config4); 461 | + #endif 462 | } 463 | 464 | void helper_mtc0_config5(CPUMIPSState *env, target_ulong arg1) 465 | @@ -1311,6 +1365,12 @@ void helper_mtc0_config5(CPUMIPSState *env, target_ulong arg1) 466 | env->CP0_EntryHi_ASID_mask = (env->CP0_Config5 & (1 << CP0C5_MI)) ? 467 | 0x0 : (env->CP0_Config4 & (1 << CP0C4_AE)) ? 0x3ff : 0xff; 468 | compute_hflags(env); 469 | + 470 | + #if defined(TARGET_MIPS64) 471 | + qemu_log_mask(CPU_LOG_MMUSHELL, "Config5=" TARGET_FMT_lx "\n", (unsigned long int)env->CP0_Config5); 472 | + #else 473 | + qemu_log_mask(CPU_LOG_MMUSHELL, "Config5=" TARGET_FMT_lx "\n", env->CP0_Config5); 474 | + #endif 475 | } 476 | 477 | void helper_mtc0_lladdr(CPUMIPSState *env, target_ulong arg1) 478 | diff --git a/target/mips/helper.c b/target/mips/helper.c 479 | index afd78b1990..09a30bbebe 100644 480 | --- a/target/mips/helper.c 481 | +++ b/target/mips/helper.c 482 | @@ -523,6 +523,7 @@ static void raise_mmu_exception(CPUMIPSState *env, target_ulong address, 483 | } 484 | env->CP0_Context = (env->CP0_Context & ~0x007fffff) | 485 | ((address >> 9) & 0x007ffff0); 486 | + qemu_log_mask(CPU_LOG_MMUSHELL, "Context=" TARGET_FMT_lx "\n", env->CP0_Context); 487 | env->CP0_EntryHi = (env->CP0_EntryHi & env->CP0_EntryHi_ASID_mask) | 488 | (env->CP0_EntryHi & (1 << CP0EnHi_EHINV)) | 489 | (address & (TARGET_PAGE_MASK << 1)); 490 | @@ -871,6 +872,7 @@ refill: 491 | env->CP0_PageMask = pw_pagemask; 492 | env->CP0_EntryLo0 = pw_entrylo0; 493 | env->CP0_EntryLo1 = pw_entrylo1; 494 | + qemu_log_mask(CPU_LOG_MMUSHELL, "PageMask=" TARGET_FMT_lx "\n", env->CP0_PageMask); 495 | 496 | /* 497 | * The hardware page walker inserts a page into the TLB in a manner 498 | @@ -883,6 +885,7 @@ refill: 499 | env->CP0_PageMask = tmp_pagemask; 500 | env->CP0_EntryLo0 = tmp_entrylo0; 501 | env->CP0_EntryLo1 = tmp_entrylo1; 502 | + qemu_log_mask(CPU_LOG_MMUSHELL, "PageMask=" TARGET_FMT_lx "\n", env->CP0_PageMask); 503 | } 504 | return true; 505 | } 506 | diff --git a/target/mips/op_helper.c b/target/mips/op_helper.c 507 | index 9552b280e0..007976b8f1 100644 508 | --- a/target/mips/op_helper.c 509 | +++ b/target/mips/op_helper.c 510 | @@ -844,6 +844,13 @@ void r4k_helper_tlbr(CPUMIPSState *env) 511 | ((uint64_t)tlb->XI1 << CP0EnLo_XI) | (tlb->C1 << 3) | 512 | get_entrylo_pfn_from_tlb(tlb->PFN[1] >> 12); 513 | } 514 | + 515 | + #if defined(TARGET_MIPS64) 516 | + qemu_log_mask(CPU_LOG_MMUSHELL, "PageMask=" TARGET_FMT_lx "\n", (unsigned long int)env->CP0_PageMask); 517 | + #else 518 | + qemu_log_mask(CPU_LOG_MMUSHELL, "PageMask=" TARGET_FMT_lx "\n", (unsigned int)env->CP0_PageMask); 519 | + #endif 520 | + 521 | } 522 | 523 | void helper_tlbwi(CPUMIPSState *env) 524 | diff --git a/target/mips/translate.c b/target/mips/translate.c 525 | index 25b595a17d..dc93eb1d4b 100644 526 | --- a/target/mips/translate.c 527 | +++ b/target/mips/translate.c 528 | @@ -7945,7 +7945,7 @@ static void gen_mtc0(DisasContext *ctx, TCGv arg, int reg, int sel) 529 | check_pw(ctx); 530 | gen_mtc0_store32(arg, offsetof(CPUMIPSState, CP0_PWBase)); 531 | register_name = "PWBase"; 532 | - break; 533 | + break; 534 | case CP0_REG05__PWFIELD: 535 | check_pw(ctx); 536 | gen_helper_mtc0_pwfield(cpu_env, arg); 537 | @@ -8475,7 +8475,7 @@ static void gen_mtc0(DisasContext *ctx, TCGv arg, int reg, int sel) 538 | tcg_gen_st_tl(arg, cpu_env, 539 | offsetof(CPUMIPSState, CP0_KScratch[sel - 2])); 540 | register_name = "KScratch"; 541 | - break; 542 | + break; 543 | default: 544 | goto cp0_unimplemented; 545 | } 546 | @@ -10442,7 +10442,14 @@ static void gen_cp0(CPUMIPSState *env, DisasContext *ctx, uint32_t opc, 547 | gen_load_gpr(t0, rt); 548 | gen_mtc0(ctx, t0, rd, ctx->opcode & 0x7); 549 | tcg_temp_free(t0); 550 | - } 551 | + 552 | + // Very poor but functional... 553 | + int sel = (ctx->opcode & 0x7); 554 | + if((rd == 5) & (sel == 5)) 555 | + qemu_log_mask(CPU_LOG_MMUSHELL, "PWBase=" TARGET_FMT_lx "\n", env->CP0_PWBase); 556 | + if((rd == 31) & (sel >= 2) & (sel <= 7)) 557 | + qemu_log_mask(CPU_LOG_MMUSHELL, "KScratch%d=" TARGET_FMT_lx "\n", sel - 1, env->CP0_KScratch[sel - 2]); 558 | + } 559 | opn = "mtc0"; 560 | break; 561 | #if defined(TARGET_MIPS64) 562 | @@ -31045,6 +31052,28 @@ void mips_cpu_dump_state(CPUState *cs, FILE *f, int flags) 563 | CPUMIPSState *env = &cpu->env; 564 | int i; 565 | 566 | + qemu_fprintf(f, "Context " TARGET_FMT_lx "\n",env->CP0_Context); 567 | + qemu_fprintf(f, "ContextConfig: Unimplemented!\n"); 568 | + qemu_fprintf(f, "PageMask 0x%08x\n",env->CP0_PageMask); 569 | + qemu_fprintf(f, "PageGrain 0x%08x\n",env->CP0_PageGrain); 570 | + qemu_fprintf(f, "SegCtl0 " TARGET_FMT_lx "\n",env->CP0_SegCtl0); 571 | + qemu_fprintf(f, "SegCtl1 " TARGET_FMT_lx "\n",env->CP0_SegCtl1); 572 | + qemu_fprintf(f, "SegCtl2 " TARGET_FMT_lx "\n",env->CP0_SegCtl2); 573 | + qemu_fprintf(f, "PWBase " TARGET_FMT_lx "\n",env->CP0_PWBase); 574 | + qemu_fprintf(f, "PWField " TARGET_FMT_lx "\n",env->CP0_PWField); 575 | + qemu_fprintf(f, "PWSize " TARGET_FMT_lx "\n",env->CP0_PWSize); 576 | + qemu_fprintf(f, "Wired 0x%08x\n",env->CP0_Wired); 577 | + qemu_fprintf(f, "PWCtl 0x%08x\n",env->CP0_PWCtl); 578 | + qemu_fprintf(f, "EBase " TARGET_FMT_lx "\n",env->CP0_EBase); 579 | + qemu_fprintf(f, "Config 0x%08x\n",env->CP0_Config0); 580 | + qemu_fprintf(f, "Config1 0x%08x\n",env->CP0_Config1); 581 | + qemu_fprintf(f, "Config2 0x%08x\n",env->CP0_Config2); 582 | + qemu_fprintf(f, "Config3 0x%08x\n",env->CP0_Config3); 583 | + qemu_fprintf(f, "Config4 0x%08x\n",env->CP0_Config4); 584 | + qemu_fprintf(f, "Config5 0x%08x\n",env->CP0_Config5); 585 | + qemu_fprintf(f, "PRiD 0x%08x\n",env->CP0_PRid); 586 | + 587 | + 588 | qemu_fprintf(f, "pc=0x" TARGET_FMT_lx " HI=0x" TARGET_FMT_lx 589 | " LO=0x" TARGET_FMT_lx " ds %04x " 590 | TARGET_FMT_lx " " TARGET_FMT_ld "\n", 591 | @@ -31073,6 +31102,7 @@ void mips_cpu_dump_state(CPUState *cs, FILE *f, int flags) 592 | env->CP0_Config4, env->CP0_Config5); 593 | if ((flags & CPU_DUMP_FPU) && (env->hflags & MIPS_HFLAG_FPU)) { 594 | fpu_dump_state(env, f, flags); 595 | + 596 | } 597 | } 598 | 599 | @@ -31243,6 +31273,18 @@ void cpu_state_reset(CPUMIPSState *env) 600 | env->msair = env->cpu_model->MSAIR; 601 | env->insn_flags = env->cpu_model->insn_flags; 602 | 603 | +#if defined(TARGET_MIPS64) 604 | + qemu_log_mask(CPU_LOG_MMUSHELL, "PageGrain=" TARGET_FMT_lx "\n", (unsigned long int)env->CP0_PageGrain); 605 | + qemu_log_mask(CPU_LOG_MMUSHELL, "Config=" TARGET_FMT_lx "\n", (unsigned long int)env->CP0_Config0); 606 | + qemu_log_mask(CPU_LOG_MMUSHELL, "Config4=" TARGET_FMT_lx "\n", (unsigned long int)env->CP0_Config4); 607 | + qemu_log_mask(CPU_LOG_MMUSHELL, "Config5=" TARGET_FMT_lx "\n", (unsigned long int)env->CP0_Config5); 608 | +#else 609 | + qemu_log_mask(CPU_LOG_MMUSHELL, "PageGrain=" TARGET_FMT_lx "\n", (unsigned int)env->CP0_PageGrain); 610 | + qemu_log_mask(CPU_LOG_MMUSHELL, "Config=" TARGET_FMT_lx "\n", (unsigned int)env->CP0_Config0); 611 | + qemu_log_mask(CPU_LOG_MMUSHELL, "Config4=" TARGET_FMT_lx "\n", (unsigned int)env->CP0_Config4); 612 | + qemu_log_mask(CPU_LOG_MMUSHELL, "Config5=" TARGET_FMT_lx "\n", (unsigned int)env->CP0_Config5); 613 | +#endif 614 | + 615 | #if defined(CONFIG_USER_ONLY) 616 | env->CP0_Status = (MIPS_HFLAG_UM << CP0St_KSU); 617 | # ifdef TARGET_MIPS64 618 | @@ -31288,11 +31330,25 @@ void cpu_state_reset(CPUMIPSState *env) 619 | env->CP0_Wired = 0; 620 | env->CP0_GlobalNumber = (cs->cpu_index & 0xFF) << CP0GN_VPId; 621 | env->CP0_EBase = (cs->cpu_index & 0x3FF); 622 | + 623 | + #if defined(TARGET_MIPS64) 624 | + qemu_log_mask(CPU_LOG_MMUSHELL, "Wired=" TARGET_FMT_lx "\n", (unsigned long int)env->CP0_Wired); 625 | + #else 626 | + qemu_log_mask(CPU_LOG_MMUSHELL, "Wired=" TARGET_FMT_lx "\n", (unsigned int)env->CP0_Wired); 627 | + #endif 628 | + 629 | if (mips_um_ksegs_enabled()) { 630 | env->CP0_EBase |= 0x40000000; 631 | } else { 632 | env->CP0_EBase |= (int32_t)0x80000000; 633 | } 634 | + 635 | + #if defined(TARGET_MIPS64) 636 | + qemu_log_mask(CPU_LOG_MMUSHELL, "EBase=" TARGET_FMT_lx "\n", (unsigned long int)env->CP0_EBase); 637 | + #else 638 | + qemu_log_mask(CPU_LOG_MMUSHELL, "EBase=" TARGET_FMT_lx "\n", (unsigned int)env->CP0_EBase); 639 | + #endif 640 | + 641 | if (env->CP0_Config3 & (1 << CP0C3_CMGCR)) { 642 | env->CP0_CMGCRBase = 0x1fbf8000 >> 4; 643 | } 644 | @@ -31367,6 +31423,11 @@ void cpu_state_reset(CPUMIPSState *env) 645 | (1 << CP0SC_EU) | (2 << CP0SC_C)) << 16; 646 | /* XKPhys (note, SegCtl2.XR = 0, so XAM won't be used) */ 647 | env->CP0_SegCtl1 |= (CP0SC_AM_UK << CP0SC1_XAM); 648 | + 649 | + qemu_log_mask(CPU_LOG_MMUSHELL, "SegCtl0=" TARGET_FMT_lx "\n", env->CP0_SegCtl0); 650 | + qemu_log_mask(CPU_LOG_MMUSHELL, "SegCtl1=" TARGET_FMT_lx "\n", env->CP0_SegCtl1); 651 | + qemu_log_mask(CPU_LOG_MMUSHELL, "SegCtl2=" TARGET_FMT_lx "\n", env->CP0_SegCtl2); 652 | + 653 | #endif 654 | if ((env->insn_flags & ISA_MIPS32R6) && 655 | (env->active_fpu.fcr0 & (1 << FCR0_F64))) { 656 | @@ -31383,6 +31444,7 @@ void cpu_state_reset(CPUMIPSState *env) 657 | /* PRI = 12 */ 658 | /* PTEI = 2 */ 659 | env->CP0_PWField = 0x0C30C302; 660 | + qemu_log_mask(CPU_LOG_MMUSHELL, "PWSize=" TARGET_FMT_lx "\n", env->CP0_PWSize); 661 | } else { 662 | /* GDI = 0 */ 663 | /* UDI = 0 */ 664 | @@ -31392,6 +31454,8 @@ void cpu_state_reset(CPUMIPSState *env) 665 | env->CP0_PWField = 0x02; 666 | } 667 | 668 | + qemu_log_mask(CPU_LOG_MMUSHELL, "PWField=" TARGET_FMT_lx "\n", env->CP0_PWField); 669 | + 670 | if (env->CP0_Config3 & (1 << CP0C3_ISA) & (1 << (CP0C3_ISA + 1))) { 671 | /* microMIPS on reset when Config3.ISA is 3 */ 672 | env->hflags |= MIPS_HFLAG_M16; 673 | diff --git a/target/mips/translate_init.inc.c b/target/mips/translate_init.inc.c 674 | index 6d145a905a..482cfe2123 100644 675 | --- a/target/mips/translate_init.inc.c 676 | +++ b/target/mips/translate_init.inc.c 677 | @@ -366,7 +366,7 @@ const mips_def_t mips_defs[] = 678 | }, 679 | { 680 | /* FIXME: 681 | - * Config3: CMGCR, PW, VZ, CTXTC, CDMM, TL 682 | + * Config3: VZ, CTXTC, CDMM, TL 683 | * Config4: MMUExtDef 684 | * Config5: MRP 685 | * FIR(FCR0): Has2008 686 | @@ -380,10 +380,11 @@ const mips_def_t mips_defs[] = 687 | (2 << CP0C1_DS) | (4 << CP0C1_DL) | (3 << CP0C1_DA) | 688 | (1 << CP0C1_PC) | (1 << CP0C1_FP), 689 | .CP0_Config2 = MIPS_CONFIG2, 690 | - .CP0_Config3 = MIPS_CONFIG3 | (1U << CP0C3_M) | (1 << CP0C3_MSAP) | 691 | + .CP0_Config3 = MIPS_CONFIG3 | (1U << CP0C3_M) | 692 | + (1 << CP0C3_CMGCR) | (1 << CP0C3_MSAP) | 693 | (1 << CP0C3_BP) | (1 << CP0C3_BI) | (1 << CP0C3_SC) | 694 | - (1 << CP0C3_ULRI) | (1 << CP0C3_RXI) | (1 << CP0C3_LPA) | 695 | - (1 << CP0C3_VInt), 696 | + (1 << CP0C3_PW) | (1 << CP0C3_ULRI) | (1 << CP0C3_RXI) | 697 | + (1 << CP0C3_LPA) | (1 << CP0C3_VInt), 698 | .CP0_Config4 = MIPS_CONFIG4 | (1U << CP0C4_M) | (2 << CP0C4_IE) | 699 | (0x1c << CP0C4_KScrExist), 700 | .CP0_Config4_rw_bitmask = 0, 701 | diff --git a/target/ppc/misc_helper.c b/target/ppc/misc_helper.c 702 | index 55b68d1246..0c2a35314e 100644 703 | --- a/target/ppc/misc_helper.c 704 | +++ b/target/ppc/misc_helper.c 705 | @@ -36,8 +36,17 @@ void helper_load_dump_spr(CPUPPCState *env, uint32_t sprn) 706 | 707 | void helper_store_dump_spr(CPUPPCState *env, uint32_t sprn) 708 | { 709 | - qemu_log("Write SPR %d %03x <= " TARGET_FMT_lx "\n", sprn, sprn, 710 | - env->spr[sprn]); 711 | + #ifdef PPC_DUMP_SPR_ACCESSES 712 | + qemu_log("Write SPR %d %03x <= " TARGET_FMT_lx "\n", sprn, sprn, 713 | + env->spr[sprn]); 714 | + #endif 715 | + 716 | + #ifdef TARGET_PPC64 717 | + if(sprn == SPR_HRMOR) 718 | + qemu_log_mask(CPU_LOG_MMUSHELL, "HRMOR=%016" PRIx64 "\n", env->spr[SPR_HRMOR]); 719 | + else if (sprn == SPR_RMOR) 720 | + qemu_log_mask(CPU_LOG_MMUSHELL, "RMOR=%016" PRIx64 "\n", env->spr[SPR_RMOR]); 721 | + #endif 722 | } 723 | 724 | #ifdef TARGET_PPC64 725 | diff --git a/target/ppc/mmu-hash32.c b/target/ppc/mmu-hash32.c 726 | index 55cf156a0b..8546448d15 100644 727 | --- a/target/ppc/mmu-hash32.c 728 | +++ b/target/ppc/mmu-hash32.c 729 | @@ -376,6 +376,8 @@ static hwaddr ppc_hash32_htab_lookup(PowerPCCPU *cpu, 730 | hash = vsid ^ pgidx; 731 | ptem = (vsid << 7) | (pgidx >> 10); 732 | 733 | + qemu_log_mask(CPU_LOG_MMUSHELL, "SDR1=" TARGET_FMT_lx "|vsid=%" PRIx32 "\n", cpu->env.spr[SPR_SDR1], vsid); 734 | + 735 | /* Page address translation */ 736 | qemu_log_mask(CPU_LOG_MMU, "htab_base " TARGET_FMT_plx 737 | " htab_mask " TARGET_FMT_plx 738 | diff --git a/target/ppc/mmu-hash64.c b/target/ppc/mmu-hash64.c 739 | index e5baabf0e1..5b1d6db45c 100644 740 | --- a/target/ppc/mmu-hash64.c 741 | +++ b/target/ppc/mmu-hash64.c 742 | @@ -59,6 +59,7 @@ static ppc_slb_t *slb_lookup(PowerPCCPU *cpu, target_ulong eaddr) 743 | 744 | LOG_SLB("%s: slot %d %016" PRIx64 " %016" 745 | PRIx64 "\n", __func__, n, slb->esid, slb->vsid); 746 | + 747 | /* 748 | * We check for 1T matches on all MMUs here - if the MMU 749 | * doesn't have 1T segment support, we will have prevented 1T 750 | @@ -660,6 +661,8 @@ static hwaddr ppc_hash64_htab_lookup(PowerPCCPU *cpu, 751 | ptem = (slb->vsid & SLB_VSID_PTEM) | ((epn >> 16) & HPTE64_V_AVPN); 752 | ptem |= HPTE64_V_VALID; 753 | 754 | + qemu_log_mask(CPU_LOG_MMUSHELL, "SDR1=" TARGET_FMT_lx "|vsid=" TARGET_FMT_lx "\n", cpu->env.spr[SPR_SDR1], vsid); 755 | + 756 | /* Page address translation */ 757 | qemu_log_mask(CPU_LOG_MMU, 758 | "htab_base " TARGET_FMT_plx " htab_mask " TARGET_FMT_plx 759 | diff --git a/target/ppc/mmu_helper.c b/target/ppc/mmu_helper.c 760 | index 86c667b094..53b3c00135 100644 761 | --- a/target/ppc/mmu_helper.c 762 | +++ b/target/ppc/mmu_helper.c 763 | @@ -1836,6 +1836,9 @@ static inline void do_invalidate_BAT(CPUPPCState *env, target_ulong BATu, 764 | static inline void dump_store_bat(CPUPPCState *env, char ID, int ul, int nr, 765 | target_ulong value) 766 | { 767 | + qemu_log_mask(CPU_LOG_MMUSHELL, "%cBAT%d%c=" TARGET_FMT_lx "\n", ID, 768 | + nr, ul == 0 ? 'U' : 'L', value); 769 | + 770 | LOG_BATS("Set %cBAT%d%c to " TARGET_FMT_lx " (" TARGET_FMT_lx ")\n", ID, 771 | nr, ul == 0 ? 'u' : 'l', value, env->nip); 772 | } 773 | @@ -2087,6 +2090,9 @@ void ppc_tlb_invalidate_one(CPUPPCState *env, target_ulong addr) 774 | void ppc_store_sdr1(CPUPPCState *env, target_ulong value) 775 | { 776 | PowerPCCPU *cpu = env_archcpu(env); 777 | + 778 | + qemu_log_mask(CPU_LOG_MMUSHELL, "SDR1=" TARGET_FMT_lx "|vsid=-1\n", value); 779 | + 780 | qemu_log_mask(CPU_LOG_MMU, "%s: " TARGET_FMT_lx "\n", __func__, value); 781 | assert(!cpu->vhyp); 782 | #if defined(TARGET_PPC64) 783 | @@ -2117,6 +2123,8 @@ void ppc_store_ptcr(CPUPPCState *env, target_ulong value) 784 | target_ulong ptcr_mask = PTCR_PATB | PTCR_PATS; 785 | target_ulong patbsize = value & PTCR_PATS; 786 | 787 | + qemu_log_mask(CPU_LOG_MMUSHELL, "PTCR=" TARGET_FMT_lx "\n", value); 788 | + 789 | qemu_log_mask(CPU_LOG_MMU, "%s: " TARGET_FMT_lx "\n", __func__, value); 790 | 791 | assert(!cpu->vhyp); 792 | @@ -2153,6 +2161,8 @@ target_ulong helper_load_sr(CPUPPCState *env, target_ulong sr_num) 793 | 794 | void helper_store_sr(CPUPPCState *env, target_ulong srnum, target_ulong value) 795 | { 796 | + qemu_log_mask(CPU_LOG_MMUSHELL, "SR%d=" TARGET_FMT_lx "\n", (int)srnum, value); 797 | + 798 | qemu_log_mask(CPU_LOG_MMU, 799 | "%s: reg=%d " TARGET_FMT_lx " " TARGET_FMT_lx "\n", __func__, 800 | (int)srnum, value, env->sr[srnum]); 801 | diff --git a/target/ppc/translate.c b/target/ppc/translate.c 802 | index 807d14faaa..de2a32069b 100644 803 | --- a/target/ppc/translate.c 804 | +++ b/target/ppc/translate.c 805 | @@ -37,6 +37,7 @@ 806 | #include "exec/log.h" 807 | #include "qemu/atomic128.h" 808 | 809 | +#include "include/qemu/log.h" 810 | 811 | #define CPU_SINGLE_STEP 0x1 812 | #define CPU_BRANCH_STEP 0x2 813 | @@ -7690,9 +7691,13 @@ void ppc_cpu_dump_state(CPUState *cs, FILE *f, int flags) 814 | #endif 815 | if (env->spr_cb[SPR_SDR1].name) { /* SDR1 Exists */ 816 | qemu_fprintf(f, " SDR1 " TARGET_FMT_lx " ", env->spr[SPR_SDR1]); 817 | - } 818 | + 819 | + qemu_log_mask(CPU_LOG_MMUSHELL, "SDR1=" TARGET_FMT_lx "|vsid=-1\n", env->spr[SPR_SDR1]); 820 | + } 821 | if (env->spr_cb[SPR_PTCR].name) { /* PTCR Exists */ 822 | qemu_fprintf(f, " PTCR " TARGET_FMT_lx " ", env->spr[SPR_PTCR]); 823 | + 824 | + qemu_log_mask(CPU_LOG_MMUSHELL, "PTCR=" TARGET_FMT_lx "\n", env->spr[SPR_PTCR]); 825 | } 826 | qemu_fprintf(f, " DAR " TARGET_FMT_lx " DSISR " TARGET_FMT_lx "\n", 827 | env->spr[SPR_DAR], env->spr[SPR_DSISR]); 828 | diff --git a/target/ppc/translate_init.inc.c b/target/ppc/translate_init.inc.c 829 | index e853164a86..5bee587428 100644 830 | --- a/target/ppc/translate_init.inc.c 831 | +++ b/target/ppc/translate_init.inc.c 832 | @@ -69,11 +69,9 @@ static void spr_read_generic(DisasContext *ctx, int gprn, int sprn) 833 | 834 | static void spr_store_dump_spr(int sprn) 835 | { 836 | -#ifdef PPC_DUMP_SPR_ACCESSES 837 | TCGv_i32 t0 = tcg_const_i32(sprn); 838 | gen_helper_store_dump_spr(cpu_env, t0); 839 | tcg_temp_free_i32(t0); 840 | -#endif 841 | } 842 | 843 | static void spr_write_generic(DisasContext *ctx, int sprn, int gprn) 844 | diff --git a/target/riscv/csr.c b/target/riscv/csr.c 845 | index 11d184cd16..5dc357956c 100644 846 | --- a/target/riscv/csr.c 847 | +++ b/target/riscv/csr.c 848 | @@ -529,6 +529,9 @@ static int write_mtvec(CPURISCVState *env, int csrno, target_ulong val) 849 | } else { 850 | qemu_log_mask(LOG_UNIMP, "CSR_MTVEC: reserved mode not supported\n"); 851 | } 852 | + 853 | + qemu_log_mask(CPU_LOG_MMUSHELL, "MTVEC=" TARGET_FMT_lx "\n", val); 854 | + 855 | return 0; 856 | } 857 | 858 | @@ -717,6 +720,9 @@ static int write_stvec(CPURISCVState *env, int csrno, target_ulong val) 859 | } else { 860 | qemu_log_mask(LOG_UNIMP, "CSR_STVEC: reserved mode not supported\n"); 861 | } 862 | + 863 | + qemu_log_mask(CPU_LOG_MMUSHELL, "STVEC=" TARGET_FMT_lx "\n", val); 864 | + 865 | return 0; 866 | } 867 | 868 | @@ -847,6 +853,9 @@ static int write_satp(CPURISCVState *env, int csrno, target_ulong val) 869 | env->satp = val; 870 | } 871 | } 872 | + 873 | + qemu_log_mask(CPU_LOG_MMUSHELL, "SATP=" TARGET_FMT_lx "\n", val); 874 | + 875 | return 0; 876 | } 877 | 878 | diff --git a/util/log.c b/util/log.c 879 | index 2da6cb31dc..bda0e7f53d 100644 880 | --- a/util/log.c 881 | +++ b/util/log.c 882 | @@ -333,7 +333,9 @@ const QEMULogItem qemu_log_items[] = { 883 | { CPU_LOG_PLUGIN, "plugin", "output from TCG plugins\n"}, 884 | #endif 885 | { LOG_STRACE, "strace", 886 | - "log every user-mode syscall, its input, and its result" }, 887 | + "log every user-mode syscall, its input, and its result\n" }, 888 | + { CPU_LOG_MMUSHELL, "mmushell", 889 | + "collect changes in MMU registers to use with mmushell"}, 890 | { 0, NULL, NULL }, 891 | }; 892 | 893 | -------------------------------------------------------------------------------- /qemu/requirements.txt: -------------------------------------------------------------------------------- 1 | qmp 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | wheel 2 | qmp 3 | prettytable 4 | tqdm 5 | colorama 6 | git+git://github.com/cea-sec/miasm.git@218492cd10b339a8d47d2fdbd61953fcf954fb8b#egg=miasm 7 | pyparsing 8 | future 9 | portion 10 | pyyaml 11 | compress_pickle[lz4] 12 | more-itertools 13 | ipython 14 | cerberus 15 | sortedcontainers 16 | numpy 17 | pyelftools 18 | --------------------------------------------------------------------------------