sim: Add debugging ptable to image dumps

Add a `debug_dump()` method to `Images` to allow the images to be
written to a file.  The dependency test will call this if the
environment variable MCUBOOT_DEBUG_DUMP is set.

In order to make these debug dumps more useful, add a simple partition
table to the beginning of the image (where MCUboot would reside on
target).  This has a simple header, and then entries for each partition,
using the partition ids used within the simulator.  This allows the
image to be more easily used by external tools.

As an example, `scripts/mcubin.bt` is a binary template for the [010
Editor](https://www.sweetscape.com/010editor/), allowing it to decode
and show the details of images from MCUboot.

Signed-off-by: David Brown <david.brown@linaro.org>
This commit is contained in:
David Brown 2019-08-13 14:29:51 -06:00 committed by David Brown
parent ceb43f5f98
commit 297029ab72
4 changed files with 228 additions and 6 deletions

135
scripts/mcubin.bt Normal file
View File

@ -0,0 +1,135 @@
// Copyright (C) 2019, Linaro Ltd
//
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// This file is a Binary Template file for the 010 Editor
// (http://www.sweetscape.com/010editor/) to allow it to show the
// structure of an MCUboot image.
LittleEndian();
struct ENTRY {
uint32 id;
uint32 offset;
uint32 size;
uint32 pad;
};
// The simulator writes the partition table at the beginning of the
// image, so that we can tell where the partitions are. If you are
// trying to view an image captured from a device, you can either
// construct a synthetic partition table in the file, or change code
// described below to hardcode one.
struct PTABLE {
uchar pheader[8];
if (ptable.pheader != "mcuboot\0") {
// NOTE: Put code here to hard code a partition table, and
// continue.
Warning("Invalid magic on ptable header");
return -1;
} else {
uint32 count;
struct ENTRY entries[count];
}
};
struct PTABLE ptable;
struct IMAGE_VERSION {
uchar major;
uchar minor;
uint16 revision;
uint32 build_num;
};
struct IHDR {
uint32 magic <format=hex>;
uint32 load_addr <format=hex>;
uint16 hdr_size <format=hex>;
uint16 protect_size <format=hex>;
uint32 img_size <format=hex>;
uint32 flags;
struct IMAGE_VERSION ver;
uint32 _pad1;
};
struct TLV_HDR {
uint16 magic;
uint16 tlv_tot;
};
struct TLV {
uchar type <format=hex>;
uchar pad;
uint16 len;
switch (type) {
case 0x01: // keyhash
uchar keyhash[len];
break;
case 0x40: // dependency
if (len != 12) {
Warning("Invalid dependency size");
return -1;
}
uchar image_id;
uchar pad1;
uint16 pad2;
struct IMAGE_VERSION version;
break;
default:
// Other, just consume the data.
uchar data[len];
}
};
local int i;
local int epos;
for (i = 0; i < ptable.count; i++) {
FSeek(ptable.entries[i].offset);
switch (ptable.entries[i].id) {
case 1:
case 2:
case 4:
case 5:
struct IMAGE {
struct IHDR ihdr;
if (ihdr.magic == 0x96f3b83d) {
uchar payload[ihdr.img_size];
epos = FTell();
struct TLV_HDR tlv_hdr;
if (tlv_hdr.magic == 0x6907) {
epos += tlv_hdr.tlv_tot;
while (FTell() < epos) {
struct TLV tlv;
}
}
}
// uchar block[ptable.entries[i].size];
} image;
break;
case 3:
struct SCRATCH {
uchar data[ptable.entries[i].size];
} scratch;
break;
default:
break;
}
}

View File

@ -136,6 +136,11 @@ impl AreaDesc {
areas
}
/// Return an iterator over all `FlashArea`s present.
pub fn iter_areas(&self) -> impl Iterator<Item = &FlashArea> {
self.whole.iter()
}
}
/// The area descriptor, C format.
@ -189,9 +194,9 @@ impl Default for FlashId {
#[repr(C)]
#[derive(Debug, Clone, Default)]
pub struct FlashArea {
flash_id: FlashId,
device_id: u8,
pub flash_id: FlashId,
pub device_id: u8,
pad16: u16,
off: u32,
size: u32,
pub off: u32,
pub size: u32,
}

View File

@ -1,9 +1,19 @@
use log::{info, warn, error};
use byteorder::{
LittleEndian, WriteBytesExt,
};
use log::{
Level::Info,
error,
info,
log_enabled,
warn,
};
use rand::{
distributions::{IndependentSample, Range},
Rng, SeedableRng, XorShiftRng,
};
use std::{
collections::HashSet,
io::{Cursor, Write},
mem,
slice,
@ -163,6 +173,7 @@ impl ImagesBuilder {
primaries: primaries,
upgrades: upgrades,
}}).collect();
install_ptable(&mut flash, &self.areadesc);
Images {
flash: flash,
areadesc: self.areadesc,
@ -1037,6 +1048,20 @@ impl Images {
mark_upgrade(flash, &image.slots[slot]);
}
}
/// Dump out the flash image(s) to one or more files for debugging
/// purposes. The names will be written as either "{prefix}.mcubin" or
/// "{prefix}-001.mcubin" depending on how many images there are.
pub fn debug_dump(&self, prefix: &str) {
for (id, fdev) in &self.flash {
let name = if self.flash.len() == 1 {
format!("{}.mcubin", prefix)
} else {
format!("{}-{:>0}.mcubin", prefix, id)
};
fdev.write_file(&name).unwrap();
}
}
}
/// Show the flash layout.
@ -1337,6 +1362,57 @@ fn verify_trailer(flash: &SimMultiFlash, slot: &SlotInfo,
!failed
}
/// Install a partition table. This is a simplified partition table that
/// we write at the beginning of flash so make it easier for external tools
/// to analyze these images.
fn install_ptable(flash: &mut SimMultiFlash, areadesc: &AreaDesc) {
let ids: HashSet<u8> = areadesc.iter_areas().map(|area| area.device_id).collect();
for &id in &ids {
// If there are any partitions in this device that start at 0, and
// aren't marked as the BootLoader partition, avoid adding the
// partition table. This makes it harder to view the image, but
// avoids messing up images already written.
if areadesc.iter_areas().any(|area| {
area.device_id == id &&
area.off == 0 &&
area.flash_id != FlashId::BootLoader
}) {
if log_enabled!(Info) {
let special: Vec<FlashId> = areadesc.iter_areas()
.filter(|area| area.device_id == id && area.off == 0)
.map(|area| area.flash_id)
.collect();
info!("Skipping partition table: {:?}", special);
}
break;
}
let mut buf: Vec<u8> = vec![];
write!(&mut buf, "mcuboot\0").unwrap();
// Iterate through all of the partitions in that device, and encode
// into the table.
let count = areadesc.iter_areas().filter(|area| area.device_id == id).count();
buf.write_u32::<LittleEndian>(count as u32).unwrap();
for area in areadesc.iter_areas().filter(|area| area.device_id == id) {
buf.write_u32::<LittleEndian>(area.flash_id as u32).unwrap();
buf.write_u32::<LittleEndian>(area.off).unwrap();
buf.write_u32::<LittleEndian>(area.size).unwrap();
buf.write_u32::<LittleEndian>(0).unwrap();
}
let dev = flash.get_mut(&id).unwrap();
// Pad to alignment.
while buf.len() % dev.align() != 0 {
buf.push(0);
}
dev.write(0, &buf).unwrap();
}
}
/// The image header
#[repr(C)]
pub struct ImageHeader {

View File

@ -8,6 +8,7 @@ use bootsim::{
NO_DEPS,
testlog,
};
use std::env;
/// A single test, after setting up logging and such. Within the $body,
/// $arg will be bound to each device.
@ -51,8 +52,13 @@ test_shell!(dependency_combos, r, {
return;
}
for dep in TEST_DEPS {
for (index, dep) in TEST_DEPS.iter().enumerate() {
let image = r.clone().make_image(&dep, true);
if env::var_os("MCUBOOT_DEBUG_DUMP").is_some() {
let name = format!("dep-test-{:0>}", index);
image.debug_dump(&name);
}
assert!(!image.run_check_deps(&dep));
}
});