kmem: capDL memory analyzer

Script that analyzes the CAmkES-generated capDL spec for memory use.

By default the memory footprint of each "component" is displayed.
(Shared memory regions are also displayed separately as it's unclear
which of multiple components to charge.)  The -d option gives each
component a breakdown by memory type:

- "elf": data loadeed from .text or .data ELF segments,
- "bss": zero-initialized memory,
- "ipc_buffer": CAmkES-generated per-thread IPC buffers,
- "stack": CAmkES-generated thread stacks,
- "copyregion": unmapped VSpace regions, and
- "mmio": device-backed memory, typically used to access MMIO registers
   but also used to reach Renode-loaded data like the cpio archive of
   builtin applications & models

(NB: mmio + copyregion sections do not count against memory use as they
are allocated from dedicated memory that does not have physical memory
backing).

By default the last release build will be analyzed (also selected with
the -R option). The -D option can be used to select the last debug build.

Change-Id: I8ed1d6249fca13e5dfd2a10159e2399463901229
diff --git a/kmem.sh b/kmem.sh
new file mode 100755
index 0000000..c55dbdc
--- /dev/null
+++ b/kmem.sh
@@ -0,0 +1,96 @@
+#! /bin/bash
+#
+# Copyright 2022 Google LLC
+#
+# 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
+#
+#     https://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.
+
+# CAmkES system image memory analyzer.
+
+# Analyze the CAmkES-generated capDL spec for memory use.
+# By default the memory foottprint of each component is displayed.
+# The -d option will give a breakdown by memory type:
+#  elf          .text + .data
+#  bss          .bss
+#  ipc_buffer   CAmkES per-thread ipc_buffer's
+#  stack        CAmkES per-thread stack
+#  bootinfo     Bootinfo page passed by the rooteserver
+#  mmio         MMIO region (backed by devivce memory)
+#  copyregion   VSpace region (w/o backing memory)
+#
+# Note mmio + copyregion sections do not count against memory usage as
+# they are allocated from dedicated memory that does not have physical
+# memory backing.
+#
+# TODO: account for system resources
+#
+# ROOTDIR must be set to the top of the shodan development tree
+# (as done by build/setup.sh).
+
+# Usage: kmem [-d]
+
+if [[ -z "${ROOTDIR}" ]]; then
+    echo "Source build/setup.sh first"
+    exit 1
+fi
+
+TARGET=${TARGET:-riscv32-unknown-elf}
+
+# Default is a summary of release build.
+DETAILS="0"
+BUILD="release"
+
+function parseargv {
+    local usage="Usage: kmem.sh [-h|--help] [-d|--details] [-D|--debug] [-R|--release] [-s|--summary]"
+    local args=$(getopt -o dDRs --long details,debug,release,summary,help -n kmem.sh -- "$@")
+
+    set -- $args
+
+    for i; do
+        case "$1" in
+            -d|--details)
+                DETAILS="1"
+                shift
+                ;;
+
+            -s|--summary)
+                DETAILS="0"
+                shift
+                ;;
+
+            -D|--debug)
+                BUILD="wdebug"
+                shift
+                ;;
+
+            -R|--release)
+                BUILD="release"
+                shift
+                ;;
+
+            --)
+                shift
+                break
+                ;;
+
+            -h|--help|*)
+                echo "$usage" >/dev/stderr
+                exit 1
+                ;;
+        esac
+    done
+}
+
+parseargv "$@"
+
+KATA_OUT="${ROOTDIR}/out/kata/${TARGET}/${BUILD}"
+exec awk -f "${ROOTDIR}/scripts/mem.awk" "${KATA_OUT}/system.cdl" DETAILS="${DETAILS}"
diff --git a/mem.awk b/mem.awk
new file mode 100644
index 0000000..429da6c
--- /dev/null
+++ b/mem.awk
@@ -0,0 +1,127 @@
+#
+# Copyright 2022 Google LLC
+#
+# 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
+#
+#     https://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.
+
+# seL4 capDL spec memory analyzer.
+
+BEGIN {
+  in_objects = 0;
+  frame_types[1] = "elf";
+  frame_types[2] = "bss";
+  frame_types[3] = "ipc_buffer";
+  frame_types[4] = "stack";
+  frame_types[5] = "copyregion";
+  frame_types[6] = "bootinfo";
+  frame_types[7] = "mmio";
+  DETAILS = 0;
+}
+/^objects.*{/ { in_objects = 1; }
+in_objects && $3 == "frame" {
+  component = $1;
+  size = 0;
+  switch (substr($4, 2)) {
+    case /4k/: size = 4096; break;
+    case /4M/: size = 4*1024*1024; break;
+    default:
+      print "Unknown frame size", substr($4, 2);
+      break;
+  }
+
+  switch ($8) {
+    case "CDL_FrameFill_BootInfo":
+      split(component, a, "_");
+      component = a[3] "_" a[4];
+      frame_type = "bootinfo";
+      break;
+    case "CDL_FrameFill_FileData":
+      sub(".*frame_", "", component);
+      sub("_group_bin.*", "", component);
+      frame_type = "elf";
+      break;
+    default: {
+      switch (component) {
+        case /_copy_region_/:
+          sub("_copy_region.*", "", component);
+          frame_type = "copyregion";
+          break;
+        case /_frame__camkes_ipc_buffer_/:
+          sub("_frame__camkes_ipc_buffer.*", "", component);
+          frame_type = "ipc_buffer";
+          break;
+        case /^stack__camkes_stack_/:
+          sub("^stack__camkes_stack_", "", component);
+          split(component, a, "_");
+          component = a[1] "_" a[2];
+          frame_type = "stack";
+          break;
+        case /_data_[0-9]_obj/:
+          sub("_?[0-9]*_data_[0-9]_obj", "", component);
+          sub("_mmio.*", "", component);
+          frame_type = ($5 == "paddr:" ? "mmio" : "bss");
+          break;
+        default:
+          if ($5 == "paddr:") {
+            sub("_mmio.*", "", component);
+            sub("_csr_.*", "", component);
+            frame_type = "mmio";
+          } else {
+            sub(".*frame_", "", component);
+            sub("_group_bin.*", "", component);
+            frame_type = "bss";
+          }
+          break;
+      }
+      break;
+    }
+  }
+  cur_component = component;
+  if (frame_type != "copyregion" && frame_type != "mmio") {
+    # NB: copyregion's are holes in the VSpace
+    components[cur_component] += size;;
+  }
+  memory[cur_component, frame_type] += size;
+}
+in_objects && /^}/ { in_objects = 0; }
+
+END {
+  asorti(components, comps, "@ind_str_asc");
+  total = 0;
+  for (i in comps) {
+    c = comps[i];
+    ntypes = 0;
+    # per-component breakdown by frame type
+    for (j in frame_types) {
+      t = frame_types[j];
+      if (memory[c, t] != "") {
+        if (DETAILS) {
+          printf "%-36.36s %-14s %5.0f KiB\n", (ntypes > 0 ? "" : c), t, memory[c, t] / 1024.;
+        }
+        ntypes++;
+      }
+    }
+    total += components[c];
+    # per-component totals
+    if (!DETAILS) {
+        printf "%-36.36s %5.0f KiB (%s)\n", c, components[c] / 1024., components[c];
+    } else if (ntypes > 1) {
+        printf "%-36.36s %-14s %5.0f KiB (%s)\n", c, "total", components[c] / 1024., components[c];
+    }
+  }
+  # overall total
+  if (DETAILS) {
+      printf "%-36.36s %-14s %5.0f KiB (%s)\n", "Total", "", total / 1024., total;
+  } else {
+      printf "%-36.36s %5.0f KiB (%s)\n", "Total", total / 1024., total;
+  }
+}