public void prettyPrint(
      final PrintWriter out, final String prefix, final String subcontainerPrefix) {

    if (!isValid()) {
      out.print(prefix);
      out.println("# Invalid EOF");
      out.print(prefix);
      out.println("# " + invalidReason);
      out.println(container);
    }

    out.print(prefix);
    out.printf("ef00%02x # Magic and Version ( %1$d )%n", version);
    out.print(prefix);
    out.printf("01%04x # Types length ( %1$d )%n", codeSections.length * 4);
    out.print(prefix);
    out.printf("02%04x # Total code sections ( %1$d )%n", codeSections.length);
    for (int i = 0; i < codeSections.length; i++) {
      out.print(prefix);
      out.printf("  %04x # Code section %d , %1$d bytes%n", getCodeSection(i).getLength(), i);
    }
    if (subContainers.length > 0) {
      out.print(prefix);
      out.printf("03%04x # Total subcontainers ( %1$d )%n", subContainers.length);
      for (int i = 0; i < subContainers.length; i++) {
        out.print(prefix);
        out.printf("  %04x # Sub container %d, %1$d byte%n", subContainers[i].container.size(), i);
      }
    }
    out.print(prefix);
    out.printf("04%04x # Data section length(  %1$d )", dataLength);
    if (dataLength != data.size()) {
      out.printf(" (actual size %d)", data.size());
    }
    out.print(prefix);
    out.printf("%n");
    out.print(prefix);
    out.printf("    00 # Terminator (end of header)%n");
    for (int i = 0; i < codeSections.length; i++) {
      CodeSection cs = getCodeSection(i);
      out.print(prefix);
      out.printf("       # Code section %d types%n", i);
      out.print(prefix);
      out.printf("    %02x # %1$d inputs %n", cs.getInputs());
      out.print(prefix);
      out.printf(
          "    %02x # %d outputs %s%n",
          cs.isReturning() ? cs.getOutputs() : 0x80,
          cs.getOutputs(),
          cs.isReturning() ? "" : " (Non-returning function)");
      out.print(prefix);
      out.printf("  %04x # max stack:  %1$d%n", cs.getMaxStackHeight());
    }
    byte[] byteCode = container.toArray();
    for (int i = 0; i < codeSections.length; i++) {
      CodeSection cs = getCodeSection(i);
      out.print(prefix);
      out.printf(
          "       # Code section %d - in=%d out=%s height=%d%n",
          i, cs.inputs, cs.isReturning() ? cs.outputs : "non-returning", cs.maxStackHeight);
      int pc = cs.getEntryPoint();
      int endPc = pc + cs.getLength();
      while (pc < endPc) {
        out.print(prefix);
        OpcodeInfo ci = V1_OPCODES[byteCode[pc] & 0xff];

        if (ci.opcode() == RelativeJumpVectorOperation.OPCODE) {
          if (pc + 1 >= byteCode.length) {
            out.printf(
                "    %02x # [%d] %s(<truncated instruction>)%n", byteCode[pc], pc, ci.name());
            pc++;
          } else {
            int tableSize = byteCode[pc + 1] & 0xff;
            out.printf("%02x%02x", byteCode[pc], byteCode[pc + 1]);
            int calculatedTableEnd = pc + tableSize * 2 + 4;
            int lastTableEntry = Math.min(byteCode.length, calculatedTableEnd);
            for (int j = pc + 2; j < lastTableEntry; j++) {
              out.printf("%02x", byteCode[j]);
            }
            out.printf(" # [%d] %s(", pc, ci.name());
            for (int j = pc + 3; j < lastTableEntry; j += 2) {
              // j indexes to the second byte of the word, to handle mid-word truncation
              if (j != pc + 3) {
                out.print(',');
              }
              int b0 = byteCode[j - 1]; // we want the sign extension, so no `& 0xff`
              int b1 = byteCode[j] & 0xff;
              out.print(b0 << 8 | b1);
            }
            if (calculatedTableEnd > byteCode.length) {
              out.print("<truncated immediate>");
            }
            pc += tableSize * 2 + 4;
            out.print(")\n");
          }
        } else if (ci.opcode() == RelativeJumpOperation.OPCODE
            || ci.opcode() == RelativeJumpIfOperation.OPCODE) {
          if (pc + 1 >= byteCode.length) {
            out.printf("    %02x # [%d] %s(<truncated immediate>)", byteCode[pc], pc, ci.name());
          } else if (pc + 2 >= byteCode.length) {
            out.printf(
                "  %02x%02x # [%d] %s(<truncated immediate>)",
                byteCode[pc], byteCode[pc + 1], pc, ci.name());
          } else {
            int b0 = byteCode[pc + 1] & 0xff;
            int b1 = byteCode[pc + 2] & 0xff;
            short delta = (short) (b0 << 8 | b1);
            out.printf("%02x%02x%02x # [%d] %s(%d)", byteCode[pc], b0, b1, pc, ci.name(), delta);
          }
          pc += 3;
          out.printf("%n");
        } else if (ci.opcode() == ExchangeOperation.OPCODE) {
          if (pc + 1 >= byteCode.length) {
            out.printf("    %02x # [%d] %s(<truncated immediate>)", byteCode[pc], pc, ci.name());
          } else {
            int imm = byteCode[pc + 1] & 0xff;
            out.printf(
                "  %02x%02x # [%d] %s(%d, %d)",
                byteCode[pc], imm, pc, ci.name(), imm >> 4, imm & 0x0F);
          }
          pc += 2;
          out.printf("%n");
        } else {
          int advance = ci.pcAdvance();
          if (advance == 1) {
            out.print("    ");
          } else if (advance == 2) {
            out.print("  ");
          }
          out.printf("%02x", byteCode[pc]);
          for (int j = 1; j < advance && (pc + j) < byteCode.length; j++) {
            out.printf("%02x", byteCode[pc + j]);
          }
          out.printf(" # [%d] %s", pc, ci.name());
          if (advance == 2) {
            if (pc + 1 >= byteCode.length) {
              out.print("(<truncated immediate>)");
            } else {
              out.printf("(%d)", byteCode[pc + 1] & 0xff);
            }
          } else if (advance > 2) {
            out.print("(0x");
            for (int j = 1; j < advance && (pc + j) < byteCode.length; j++) {
              out.printf("%02x", byteCode[pc + j]);
            }
            if ((pc + advance) > byteCode.length) {
              out.print(" <truncated immediate>");
            }
            out.print(")");
          }
          out.printf("%n");
          pc += advance;
        }
      }
    }

    for (int i = 0; i < subContainers.length; i++) {
      var subContainer = subContainers[i];
      out.print(prefix);
      out.printf("           # Subcontainer %s%d starts here%n", subcontainerPrefix, i);

      subContainer.prettyPrint(out, prefix + "    ", subcontainerPrefix + i + ".");
      out.print(prefix);
      out.printf("           # Subcontainer %s%d ends%n", subcontainerPrefix, i);
    }

    out.print(prefix);
    if (data.isEmpty()) {
      out.print("       # Data section (empty)\n");
    } else {
      out.printf("  # Data section length ( %1$d )", dataLength);
      if (dataLength != data.size()) {
        out.printf(" actual length ( %d )", data.size());
      }
      out.printf("%n%s  %s%n", prefix, data.toUnprefixedHexString());
    }
    out.flush();
  }
A more readable representation of the hex bytes, including whitespace and comments after hashes

@param out the print writer to pretty print to
@param prefix The prefix to prepend to all output lines (useful for nested subconntainers)
@param subcontainerPrefix The prefix to add to subcontainer names.
evm/src/main/java/org/hyperledger/besu/evm/code/EOFLayout.java