<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged low-level</title>
<link>https://coderlegion.com/tag/low-level</link>
<description>Powered by Question2Answer</description>
<item>
<title>qodon - a CLI based genomics tool</title>
<link>https://coderlegion.com/28461/qodon-a-cli-based-genomics-tool</link>
<description>&lt;h2 id=&quot;qodon-qdn-&quot;&gt;qodon (qdn) 🧬&lt;/h2&gt;
&lt;h3 id=&quot;a-command-line-genomics-tool-written-in-c&quot;&gt;A Command Line Genomics tool. Written in C&lt;/h3&gt;
&lt;p&gt;qodon (invoked as &lt;code&gt;qdn&lt;/code&gt;) is an ultra-fast, lightweight command-line utility for quick genomic sequence manipulations. Written in pure C, it processes sequence arguments natively at bare-metal speeds, making it perfect for rapid testing, scripting, and pipeline integration. &lt;/p&gt;
&lt;h4 id=&quot;-features&quot;&gt;✨ Features&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Zero Overhead:&lt;/strong&gt; Built in standard C for maximum execution velocity.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dual-Mode Engine:&lt;/strong&gt; Run quick one-off commands from your terminal or drop into a stateful interactive shell context.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Instant Evaluation:&lt;/strong&gt; High-performance byte manipulation for zero-lag sequence transformations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Core Bio-Utilities:&lt;/strong&gt; Fast sequence transcription, complementing, reversing, and metric calculations.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;-installation&quot;&gt;🚀 Installation&lt;/h3&gt;
&lt;p&gt;You can install &lt;code&gt;qodon&lt;/code&gt; directly or build from the source code. &lt;/p&gt;
&lt;h4 id=&quot;1-direct-install-linux-x86-64-&quot;&gt;1. Direct Install (Linux, x86_64)&lt;/h4&gt;
&lt;p&gt;Install precompiled binaries by running: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -L -o qdn https://github.com/Temi-Tade/qodon/releases/download/v0.2.0/qdn &amp;amp;&amp;amp; chmod +x qdn &amp;amp;&amp;amp; sudo mv qdn /usr/local/bin/
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;2-build-from-source&quot;&gt;2. Build from source&lt;/h4&gt;
&lt;p&gt;Compile &lt;code&gt;qdn&lt;/code&gt; natively using any modern C compiler (&lt;code&gt;gcc&lt;/code&gt; or &lt;code&gt;clang&lt;/code&gt; required): &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Clone the repository
git clone https://github.com/Temi-Tade/qodon.git
cd qodon

# Compile the binary with optimizations
gcc src/main.c -O3 src/lib/qodon.c src/lib/shell.c -o bin/qdn

# Move to your local bin to run it globally
sudo mv qdn /usr/local/bin/
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;usage&quot;&gt;Usage&lt;/h3&gt;
&lt;h4 id=&quot;-mode-1-interactive-context-shell&quot;&gt;🐚 Mode 1: Interactive Context Shell&lt;/h4&gt;
&lt;p&gt;Launch &lt;code&gt;qdn&lt;/code&gt; with the &lt;code&gt;-i&lt;/code&gt; flag followed by a target sequence to open the interactive context shell. The sequence is loaded directly into the session's workspace memory, allowing you to run rapid back-to-back operations against that specific strand without retyping it. &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ qdn -i ATGCATGC
Entering qodon shell...
Template sequence: ATGCATGC 
Length: 8bps 
GC content: 50.00%
qdn&amp;gt; len
8bps
qdn&amp;gt; cmp
ATGCATGC (Template strand)
||||||||
TACGTACG (Complementary strand)
qdn&amp;gt; gc
50.00%
qdn&amp;gt; rev
ATGCATGC (FWD)
CGTACGTA (REV)
qdn&amp;gt; exit
$
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;available-shell-commands&quot;&gt;Available Shell Commands&lt;/h4&gt;
&lt;p&gt;Inside the shell, execute operations directly on the initialized sequence: &lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Command &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Action &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Description&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;cmp&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Complement &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Generates the complementary DNA/RNA strand&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;tr&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Transcribe &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Transcribes DNA to RNA (converts T to U)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;rt&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Reverse Transcribe &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Reverse transcribes RNA to DNA (converts U to T)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;rev&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Reverse &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Reverses the sequence direction&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;len&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Length &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Computes the total length of the sequence&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;gc&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;GC-Content &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Calculates the exact GC content percentage&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;exit&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Exit &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Closes the interactive shell session&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id=&quot;-mode-2-quick-terminal-flags&quot;&gt;🛠️ Mode 2: Quick Terminal Flags&lt;/h4&gt;
&lt;p&gt;For quick one-offs or integration into larger bash scripts, pass an operational flag followed by your target nucleotide sequence string directly to your terminal: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;qdn [flag] &amp;lt;SEQUENCE&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;core-flag-operations&quot;&gt;Core Flag Operations&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Flag &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Shell Command &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Action &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Description&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;-c&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;cmp &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Complement &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Generates the complementary DNA/RNA strand&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;-t&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;tr &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Transcribe &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Transcribes DNA to RNA (converts T to U)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;-u&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;rt &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Reverse Transcribe &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Reverse transcribes RNA to DNA (converts U to T)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;-l&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;len &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Length &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Computes the total length of the sequence&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;-g&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;gc &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;GC-Content &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Calculates the GC content percentage&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;-i&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Shell Mode &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Enter interactive shell&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id=&quot;-one-off-examples&quot;&gt;💻 One-off Examples&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Get Complementary Strand:&lt;/strong&gt; &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;qdn -c ATGC
# Output: TACG
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Transcribe DNA to RNA:&lt;/strong&gt; &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;qdn -t GATTACA
# Output: CUAAUGU
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Reverse Transcribe RNA to DNA&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;qdn -u UUAGC
# Output: AATCG
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Calculate GC Content:&lt;/strong&gt; &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;qdn -g CCGGTT
# Output: 66.67%
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Get Sequence Length:&lt;/strong&gt; &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;qdn -l ATGCATGC
# Output: 8bps
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;-license&quot;&gt;📄 License&lt;/h4&gt;
&lt;p&gt;This project is licensed under the MIT License. &lt;/p&gt;
</description>
<category>Launches</category>
<guid isPermaLink="true">https://coderlegion.com/28461/qodon-a-cli-based-genomics-tool</guid>
<pubDate>Thu, 24 Sep 2026 10:22:41 +0000</pubDate>
</item>
<item>
<title>What Math Actually Buys You: Optimizing a Rock-Paper-Scissors Game in C</title>
<link>https://coderlegion.com/27721/what-math-actually-buys-you-optimizing-a-rock-paper-scissors-game-in-c</link>
<description>&lt;p&gt;I was looking through my old projects and found a couple of Rock-Paper-Scissors games I wrote in C a few years back. Why more than one? Because programming is fun, but also because I was experimenting. We're going to look at both versions and talk about how math actually shows up in code. This will not be some generic &quot;you should know math&quot; lecture, but a before/after comparison grounded in my humble experience. &lt;/p&gt;
&lt;h3 id=&quot;why-this-matters-if-you-re-self-taught&quot;&gt;Why this matters if you're self-taught&lt;/h3&gt;
&lt;p&gt;If you're a fresh CS student or a self-taught developer, there's a decent chance you don't fully buy that math concepts matter day to day. Fair. Here's how I think about it: programming gives you a lot of room to be creative in how you solve problems, but your creativity is powered by what you actually know. Knowledge is the raw material here and math is one of the biggest sources of that raw material.&lt;/p&gt;
&lt;p&gt;Here's an example: if you wanted to write a loop in Rust with unusual control flow, and you didn't know that Rust's &lt;code&gt;while&lt;/code&gt; is really just shorthand for the common pattern using &lt;code&gt;loop&lt;/code&gt; + &lt;code&gt;if&lt;/code&gt; + &lt;code&gt;else&lt;/code&gt; + &lt;code&gt;break&lt;/code&gt;, you'd spend a long time fighting &lt;code&gt;while&lt;/code&gt; to do something it wasn't built for. If you knew that, you'd just build the custom loop you actually need.&lt;/p&gt;
&lt;p&gt;Or the other direction: a senior dev once took a buggy 12-line React component I wrote and refactored it into 3 correct lines, because he knew which hooks and methods to reach for. Your imagination still needs building blocks to arrange. Math, at a certain stage, is where a lot of those blocks come from. It gives you a precise, calculable, universal way to reason about and express a problem instead of just poking at it until it works.&lt;/p&gt;
&lt;p&gt;Let's look at actual code. The full source code for both implementations is public on GitHub &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/davidessien925/rock-paper-scissors&quot;&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;iteration-1-the-naive-version&quot;&gt;Iteration 1: the naive version&lt;/h3&gt;
&lt;p&gt;Here's the &lt;code&gt;main&lt;/code&gt; function of the first version I wrote:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int main(void) {
  srand(time(NULL));
  int random_int = (rand() % ((length_options + 1) - 1)) + 1;

  int option;

  printf(&quot;Let's play Rock Paper Scissors!\n&quot;);
  printf(&quot;First to 3 points wins!\n&quot;);

  while ((option != 1 &amp;amp;&amp;amp; option != 2 &amp;amp;&amp;amp; option != 3) ||
         (player_score != 3 &amp;amp;&amp;amp; my_score != 3)) {

    round_number += 1;

    printf(&quot;\n===============\n&quot;);
    printf(&quot;Round %d\n&quot;, round_number);

    printScores();
    printOptions();

    scanf(&quot;%d&quot;, &amp;amp;option);

    if (option == 0 || option &amp;gt; length_options) {
      printf(&quot;\nPlease select a number ranging from 1 to 3 according to your &quot;
             &quot;desired option.\n\n&quot;);
    } else {
      printf(&quot;\nYou picked %s\n&quot;, options[option - 1].name);
      printf(&quot;I picked %s\n&quot;, options[random_int - 1].name);
      declare_round_winner((option - 1), (random_int - 1));
    }
    printf(&quot;\n===============\n&quot;);
  }

  printScores();
  declare_overall_winner();
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Quick heads up before I explain anything: there's a bug in this loop condition. &lt;code&gt;option&lt;/code&gt; is declared but never given a value before the &lt;code&gt;while&lt;/code&gt; check reads it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;while ((option != 1 &amp;amp;&amp;amp; option != 2 &amp;amp;&amp;amp; option != 3) || ...)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reading an uninitialized variable in C is undefined behavior. It happens to &quot;work&quot; here because whatever garbage value is sitting in that memory slot almost never equals 1, 2, or 3, so the loop starts anyway. But that's luck, not correctness. If you're newer to C, this is a good example of a bug that won't show up as a crash, it'll just quietly rely on the right garbage value. Moving on.&lt;/p&gt;
&lt;p&gt;The supporting pieces are what you'd expect: an &lt;code&gt;Option&lt;/code&gt; struct holding a name and value, an array of three options (Rock, Paper, Scissors), score counters, a &lt;code&gt;printScores&lt;/code&gt; function, a &lt;code&gt;printOptions&lt;/code&gt; function that loops over the array so adding a fourth option later doesn't require touching the print logic, and input validation that re-prompts on an out-of-range choice.&lt;/p&gt;
&lt;p&gt;The main focal point for this post is how the winner gets decided:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int check_rules(int player_option, int my_option) {
  switch (player_option) {
  case 0:
    if (my_option == 0) return 0;
    else if (my_option == 1) return 1;
    else return -1;
  case 1:
    if (my_option == 0) return -1;
    else if (my_option == 1) return 0;
    else return 1;
  case 2:
    if (my_option == 0) return 1;
    else if (my_option == 1) return -1;
    else return 0;
  default:
    return 0;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This checks every possible value of &lt;code&gt;player_option&lt;/code&gt; against every possible value of &lt;code&gt;my_option&lt;/code&gt; and returns -1 (loss), 0 (draw), or 1 (win). It's ugly, but it's also the most direct solution I could think of at the time. It also doesn't scale. Let's say you wanted to play the more advanced and respected version of the game: Rock-Paper-Scissors-Lizard-Spock, you'd have to add Lizard and Spock. You'd also have to rewrite every branch. Sheldon wouldn't approve.&lt;/p&gt;
&lt;h3 id=&quot;iteration-2-what-changed-and-why&quot;&gt;Iteration 2: what changed, and why&lt;/h3&gt;
&lt;p&gt;Instead of guessing which part needed optimizing, I looked at the code and realized the winner-check was the ugliest and least scalable part of it so I wrote out every possible outcome as a table:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;Rock          &lt;/th&gt;&lt;th&gt;Paper        &lt;/th&gt;&lt;th&gt;Scissors&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Rock     &lt;/td&gt;&lt;td&gt;Draw          &lt;/td&gt;&lt;td&gt;Rock Loss    &lt;/td&gt;&lt;td&gt;Rock Win&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Paper    &lt;/td&gt;&lt;td&gt;Paper Win     &lt;/td&gt;&lt;td&gt;Draw         &lt;/td&gt;&lt;td&gt;Paper Loss&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Scissors &lt;/td&gt;&lt;td&gt;Scissors Loss &lt;/td&gt;&lt;td&gt;Scissors Win &lt;/td&gt;&lt;td&gt;Draw&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Then I substituted numbers for the outcomes (Win = 1, Draw = 0, Loss = -1):&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;Rock &lt;/th&gt;&lt;th&gt;Paper &lt;/th&gt;&lt;th&gt;Scissors&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Rock     &lt;/td&gt;&lt;td&gt;0    &lt;/td&gt;&lt;td&gt;-1    &lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Paper    &lt;/td&gt;&lt;td&gt;1    &lt;/td&gt;&lt;td&gt;0     &lt;/td&gt;&lt;td&gt;-1&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Scissors &lt;/td&gt;&lt;td&gt;-1   &lt;/td&gt;&lt;td&gt;1     &lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;That's a lookup table. I originally called this &quot;a matrix,&quot; but I want to be more precise here than I was when I first wrote this, because the interesting math isn't that it has a nice grid-looking shape. It's that the values in this grid aren't arbitrary. Notice the pattern. It's skew-symmetric (flip any cell across the diagonal and the sign flips), and it repeats in a cycle. This is called modular arithmetic. If you assign Rock = 0, Paper = 1, Scissors = 2, the outcome of any matchup is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;outcome = (player - opponent + 3) % 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where a result of 1 means the player wins, 2 means the player loses, and 0 is a draw (you'd remap those three values to +1/0/-1 to match the table above). That's the math concept doing the work here: a cyclic relationship expressed with the modulo operator, the same operation that underlies clock arithmetic, hashing, and a good chunk of the number theory that shows up in cryptography (RSA leans on modular exponentiation directly). A 3x3 lookup table is one way to encode that relationship.&lt;/p&gt;
&lt;p&gt;In code, the lookup table version looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int rules_matrix[3][3] = {{0, -1, 1}, {1, 0, -1}, {-1, 1, 0}};

int check_rules_matrix(int x, int y) {
  return rules_matrix[x][y];
}

void update_scores(int outcome) {
  switch (outcome) {
  case 1:
    computer_score += 1;
    printf(&quot;\nI won this round!\n&quot;);
    break;
  case -1:
    player_score += 1;
    printf(&quot;\nYou won this round!\n&quot;);
    break;
  default:
    printf(&quot;\nDraw\n&quot;);
    break;
  }
}

void declare_round_winner(int c_option, int p_option) {
  update_scores(check_rules_matrix(c_option, p_option));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Determining a winner is now a single array access instead of nine branches. Add Lizard and Spock, and you're extending a 3x3 table to a 5x5 table and updating the &lt;code&gt;options&lt;/code&gt; array. No switch statements to rewrite.&lt;/p&gt;
&lt;h3 id=&quot;does-it-actually-matter-&quot;&gt;Does it actually matter?&lt;/h3&gt;
&lt;p&gt;I wanted to check the performance claim instead of just asserting it, so I benchmarked both approaches directly, 100 million calls each, isolated from I/O:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#define ITERATIONS 100000000

int check_rules_switch(int player_option, int my_option) {
  switch (player_option) {
  case 0:
    if (my_option == 0) return 0;
    else if (my_option == 1) return 1;
    else return -1;
  case 1:
    if (my_option == 0) return -1;
    else if (my_option == 1) return 0;
    else return 1;
  case 2:
    if (my_option == 0) return 1;
    else if (my_option == 1) return -1;
    else return 0;
  default:
    return 0;
  }
}

static const int rules_matrix[3][3] = {
    { 0, -1,  1},
    { 1,  0, -1},
    {-1,  1,  0}
};

int check_rules_matrix(int x, int y) {
  return rules_matrix[x][y];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Results, run three times:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;1. Switch/Branching Method: 0.343620 seconds (3.44 ns per call)
2. Matrix Lookup Method:    0.275987 seconds (2.76 ns per call)

3. Switch/Branching Method: 0.342246 seconds (3.42 ns per call)
4. Matrix Lookup Method:    0.272867 seconds (2.73 ns per call)

5. Switch/Branching Method: 0.340185 seconds (3.40 ns per call)
6. Matrix Lookup Method:    0.272581 seconds (2.73 ns per call)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The lookup table wins consistently, by about 0.7 nanoseconds per call, roughly a 20% improvement in this specific isolated benchmark. But let's be clear on what that means. 0.7 nanoseconds is nothing in absolute terms. In a real Rock-Paper-Scissors game bottlenecked by &lt;code&gt;scanf&lt;/code&gt; waiting on a human, this difference is completely invisible.&lt;/p&gt;
&lt;p&gt;So yeah this isn't exactly going to save your production system (unless that's a specific problem your system has). The main point is that recognizing the modular structure of the problem gave me a solution that was simultaneously simpler to read, easier to extend, and measurably faster, all from the same insight. And that's how math helps. Besides making code faster, it also changes what kind of solution occurs to you in the first place.&lt;/p&gt;
&lt;p&gt;This is the fact I keep noticing as I move further into security work. A lot of what looks like &quot;cleverness&quot; in an optimized or exploited system is really just someone noticing the underlying structure before anyone else bothered to look for it.&lt;/p&gt;
&lt;h3 id=&quot;addendum-what-if-you-skip-the-table-entirely-&quot;&gt;Addendum: what if you skip the table entirely?&lt;/h3&gt;
&lt;p&gt;If the outcome is really just modular arithmetic, why store a table at all? You can compute the result directly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int check_rules_mod(int x, int y) {
  return ((x - y + 4) % 3) - 1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Same return convention as &lt;code&gt;check_rules_matrix&lt;/code&gt; (1 if &lt;code&gt;x&lt;/code&gt; wins, -1 if &lt;code&gt;y&lt;/code&gt; wins, 0 for a draw). I checked this against all 9 possible matchups by brute force and it matches exactly, so correctness isn't in question.&lt;/p&gt;
&lt;p&gt;Performance is where this got more interesting than I expected, and where I want to be upfront about the limits of what I actually understand.&lt;/p&gt;
&lt;p&gt;While putting this post together, I recompiled the exact same switch-vs-matrix benchmark file from above with the &lt;code&gt;-O2&lt;/code&gt; optimization flag, mostly out of curiosity, and the ranking completely inverted:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;1. Switch/Branching Method: 0.119248 seconds (1.19 ns per call)
2. Matrix Lookup Method:    0.133597 seconds (1.34 ns per call)
 
1. Switch/Branching Method: 0.120751 seconds (1.21 ns per call)
2. Matrix Lookup Method:    0.128811 seconds (1.29 ns per call)
 
1. Switch/Branching Method: 0.122600 seconds (1.23 ns per call)
2. Matrix Lookup Method:    0.132499 seconds (1.32 ns per call)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The only thing I changed was adding the build flag, and switch went from consistently losing to consistently winning. I don't yet know enough about what the compiler is actually doing under &lt;code&gt;-O2&lt;/code&gt; to explain that properly, branching, inlining, and instruction-level optimization are a whole subject I haven't put real time into, and I'd rather say that plainly than dress up a guess as an explanation.&lt;/p&gt;
&lt;p&gt;I also wanted numbers for the modular version above, not just correctness. Getting a fair three-way comparison meant controlling for whether the compiler inlines each function, which isn't something I know how to force myself yet, so I had AI help write a benchmark harness that pins that down explicitly. I compiled and ran it on my own machine, these are the numbers I got:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;All three functions forced to inline (best case):&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;1. Switch/Branching Method: 1.293 ns per call
2. Matrix Lookup Method:    1.339 ns per call
3. Modular Arithmetic:      1.261 ns per call
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;All three functions forced to be real function calls:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;1. Switch/Branching Method: 2.261 ns per call
2. Matrix Lookup Method:    1.697 ns per call
3. Modular Arithmetic:      1.793 ns per call
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Same story as the &lt;code&gt;-O2&lt;/code&gt; flip: which one wins depends on a compiler decision, not on the code itself. Inlined, switch is fastest and mod edges out matrix. Forced into real function calls, matrix takes over and switch falls behind badly. Mod never loses badly either way, but I'm not going to dress that up as &quot;modular arithmetic is the safe choice performance-wise&quot; when I can't yet explain why the ranking moves the way it does.&lt;/p&gt;
&lt;p&gt;Consider this a flag rather than a finding: something real is going on here, across two separate build decisions, it's outside what I currently know, and it's on the list for a proper, dedicated post once I've actually built the compiler knowledge to back it up instead of just reporting numbers I can't fully explain.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;small&gt;Cover photo by &lt;a rel=&quot;nofollow&quot; href=&quot;https://unsplash.com/@hyson?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Antony Hyson Seltran&lt;/a&gt; on &lt;a rel=&quot;nofollow&quot; href=&quot;https://unsplash.com/photos/a-pen-sitting-on-top-of-a-piece-of-paper-7RgWuLlea24?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27721/what-math-actually-buys-you-optimizing-a-rock-paper-scissors-game-in-c</guid>
<pubDate>Sat, 19 Sep 2026 12:32:40 +0000</pubDate>
</item>
<item>
<title>Are computers truly random? How chaos powers randomness.</title>
<link>https://coderlegion.com/22892/are-computers-truly-random-how-chaos-powers-randomness</link>
<description>&lt;p&gt;You would have come across randomness on a computer at a particular point; whether it is playing a game where assets spawn at random locations, writing code that requires random numbers, shuffling a playlist to play a random song next, the whole shebang. Computers use random numbers in everyday applications.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;This article contains an affiliate link to purchase OneKey crypto hardware wallets, at no extra cost to you. This helps support my articles.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This begs the question of “are computers truly random?”. How can a device with wires, circuits, logic gates and transistors that are switching on and off billions of times a second able to generate random numbers?&lt;/p&gt;
&lt;p&gt;Let me put you out of your misery, computers are not random, on their own at least.&lt;/p&gt;
&lt;h3 id=&quot;computers-are-deterministic-&quot;&gt;Computers are deterministic.&lt;/h3&gt;
&lt;p&gt;This is a fancy way of saying that for a given set of inputs, a computer would always produce the same output. A computer cannot just create a number out of thin air. It needs a starting number (seed) and a formula to produce a “random&quot; number. At a high level, this is how Pseudo Random Number Generators (PRNGs) work.&lt;/p&gt;
&lt;h3 id=&quot;the-paradox-&quot;&gt;The paradox.&lt;/h3&gt;
&lt;p&gt;This means that if someone knows the starting number and the formula the computer uses to generate a random number, one can actually generate every random number the computer has generated in the past and would ever generate.&lt;/p&gt;
&lt;p&gt;For average users like you and I, this is not a problem in our games and streaming platforms. However for advanced folks, this is an issue as they can literally ‘crack’ the system.&lt;/p&gt;
&lt;p&gt;Computers use random numbers beyond games and shuffling your playlist. Web3 wallets use random numbers to generate private keys, Password salts involve the use of random numbers among other secure applications.&lt;/p&gt;
&lt;p&gt;Imagine if someone could predict every private key you would ever generate on your device, that’s basically a disaster waiting to happen.&lt;/p&gt;
&lt;h3 id=&quot;the-solution-entropy&quot;&gt;The solution: Entropy&lt;/h3&gt;
&lt;p&gt;Computers are deterministic and attackers can steal information from memory to predict future random numbers. How then do computers generate “secure random numbers&quot;?&lt;/p&gt;
&lt;p&gt;The solution is to look outside its CPU and memory walls and get real-world data.&lt;/p&gt;
&lt;p&gt;The world we live in is a chaotic mess of ever-changing data: temperature, photons hitting a surface, traffic data among others.&lt;/p&gt;
&lt;p&gt;Computers can use this data to generate random numbers that are secure and unpredictable.&lt;/p&gt;
&lt;p&gt;How?&lt;/p&gt;
&lt;p&gt;The computer takes real-world data around it using sensors and its hardware, the data is passed into an entropy pool in the operating system and the pool is hashed using a mathematical function. When the computer needs a random number, it takes a snapshot of the pool, applies a mathematical formula with a counter and spits it out.&lt;/p&gt;
&lt;p&gt;Why this beats math for security.&lt;br&gt;
Like I said earlier, an attacker could grab the starting number and formula from memory and generate every random number the computer would ever generate.&lt;/p&gt;
&lt;p&gt;Using chaotic, real-world data that the OS dumps into its entropy pool, the computer can generate random numbers by looking outside its own self.&lt;/p&gt;
&lt;p&gt;A classic example of this is how Cloudflare uses Lava lamps - &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.cloudflare.com/learning/ssl/lava-lamp-encryption/&quot;&gt;https://www.cloudflare.com/learning/ssl/lava-lamp-encryption/&lt;/a&gt; to create secure internet connections.&lt;/p&gt;
&lt;h3 id=&quot;how-secure-is-this-&quot;&gt;How secure is this?&lt;/h3&gt;
&lt;p&gt;This method of computer randomness is so secure that it is used to generate private keys for web3 wallets, encrypt passwords and other high-risk security applications.&lt;/p&gt;
&lt;p&gt;How does it handle duplicates? Random data is added to the entropy pool every few milliseconds and the hashing formula has a property called the &lt;strong&gt;avalanche effect&lt;/strong&gt; — addition of a small new data causes a big change in the output.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;[1,3,6] — 0x1acd.... // current entropy pool

[1,3,6,1] — 0xc176....// entropy pool after addition of new data
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also, the key used with the counter to generate random bytes is destroyed every few seconds. The counter is a nonce (number that can be used once) that cannot be replayed once used.&lt;/p&gt;
&lt;p&gt;All of these factors keep the Random Number Generator truly random, very secure and unbreakable.&lt;/p&gt;
&lt;h3 id=&quot;are-humans-any-better-&quot;&gt;Are humans any better?&lt;/h3&gt;
&lt;p&gt;If you are asked right now, to &quot;quickly pick a random number between 1 and 20,&quot; your brain feels like it is making a completely free, chaotic choice. But cognitive psychologists have run this experiment thousands of times, and the results show that humans are incredibly predictable.&lt;/p&gt;
&lt;p&gt;Our brains are hardwired pattern-recognition engines. We are physically incapable of &quot;making something up&quot; without being influenced by our recent memories, what we are looking at in the room, our cultural biases, and the neural wiring of our brains.&lt;/p&gt;
&lt;p&gt;In a strange way, a computer using an entropy pool is actually mimicking us. It uses its &quot;eyes and ears&quot; (hardware sensors measuring heat and noise) to pick a number, just like we use our senses and subconscious memory to pick one.&lt;/p&gt;
&lt;p&gt;So, who is truly random?&lt;br&gt;
If &quot;true randomness&quot; means generating a number out of pure nothingness without any prior cause, then neither computers nor humans can do it. We both live in a physical universe governed by cause and effect.&lt;/p&gt;
&lt;p&gt;In my next article, we will explore how crypto hardware wallets like OneKey, use randomness to generate secure private keys.&lt;/p&gt;
&lt;p&gt;OneKey offers secure hardware wallets whose firmware and hardware designs are fully open-source and auditable right on GitHub. Backed by heavyweights like Coinbase Ventures and Dragonfly, they offer an incredible product lineup.&lt;/p&gt;
&lt;p&gt;Click the link below to get 10% off OneKey hardware wallets.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://onekey.so/r/TEMI0x0/shop&quot;&gt;Get 10% off OneKey Hardware wallets&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/22892/are-computers-truly-random-how-chaos-powers-randomness</guid>
<pubDate>Fri, 17 Jul 2026 14:51:34 +0000</pubDate>
</item>
<item>
<title>#  Breaking the 200ns Barrier: The Geometric Logic Behind Penta-V Kernel</title>
<link>https://coderlegion.com/16363/breaking-the-200ns-barrier-the-geometric-logic-behind-penta-v-kernel</link>
<description>&lt;p&gt;After weeks of fighting the Rust borrow checker and optimizing atomic operations, I finally stabilized the &lt;strong&gt;Penta-V Kernel (pvk)&lt;/strong&gt;. In this post, I’ll dive deep into why traditional linear queuing fails under extreme jitter and how &lt;strong&gt;&quot;Geometric Dissipation&quot;&lt;/strong&gt; provides a new path for structural immunity.&lt;/p&gt;
&lt;p&gt;The Penta-V Kernel isn't just a library; it's a structural insurance policy for high-load systems. By combining geometric logic with Rust's performance, it provides a decisive edge in stability and resource management.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;core-technical-benefits-amp-deep-dive&quot;&gt;Core Technical Benefits &amp;amp; Deep-Dive&lt;/h3&gt;
&lt;h4 id=&quot;1-beyond-linear-scaling-geometric-poles-&quot;&gt;1. Beyond Linear Scaling (Geometric Poles)&lt;/h4&gt;
&lt;p&gt;Traditional load balancers fail when traffic spikes exceed their buffer capacity. Instead of static buffers, Penta-V reshapes its defensive posture based on system stressors.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Geometric Dissipation:&lt;/strong&gt; As stress increases, the system &quot;reshapes&quot; itself from a Triangle to a Circle to ensure higher survival rates during sudden traffic surges.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Benefit:&lt;/strong&gt; Decoupling load logic from execution logic allows for easier auditing and faster incident response.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;2-thermal-aware-resilience-the-cooling-protocol-&quot;&gt;2. Thermal-Aware Resilience (The Cooling Protocol)&lt;/h4&gt;
&lt;p&gt;Software &quot;heat&quot; (computational stress) often leads to cascading failures. Penta-V integrates a proactive thermal-aware layer that mitigates load before the core reaches a critical state.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The Logic:&lt;/strong&gt; By reducing system impact early, we eliminate the &quot;thundering herd&quot; problem and ensure predictable behavior under extreme stress.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;3-benchmarks-amp-zero-cost-abstractions&quot;&gt;3. Benchmarks &amp;amp; Zero-Cost Abstractions&lt;/h4&gt;
&lt;p&gt;Built in 100% Rust with no external dependencies, Penta-V offers high-level geometric logic with minimal latency.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Performance:&lt;/strong&gt; Achieving sub-200ns latency (avg &lt;strong&gt;188.58ns&lt;/strong&gt;) on &lt;code&gt;![no_std]&lt;/code&gt; bare-metal environments.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Throughput:&lt;/strong&gt; Capable of handling approximately &lt;strong&gt;8.5 million&lt;/strong&gt; operations per second with very low jitter.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-target-ecosystems-amp-applications&quot;&gt;️ Target Ecosystems &amp;amp; Applications&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cloud Infrastructure &amp;amp; Edge Computing:&lt;/strong&gt; Prevents local node collapse in resource-constrained environments.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;High-Frequency Trading (FinTech):&lt;/strong&gt; Provides &quot;Structural Immunity&quot; during market volatility, ensuring the transaction pipeline remains in equilibrium.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cyber-Defense:&lt;/strong&gt; Unlike traditional buffers that drop packets, Penta-V escalates through geometric tiers to neutralize volumetric spikes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Embedded Systems &amp;amp; IoT:&lt;/strong&gt; Ideal for mission-critical hardware, allowing low-power processors to manage &quot;Digital Heat&quot; efficiently via the &lt;code&gt;#![no_std]&lt;/code&gt; core.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-roadmap-amp-future-vision&quot;&gt;️ Roadmap &amp;amp; Future Vision&lt;/h3&gt;
&lt;p&gt;This isn't just about speed; it's about building systems that are &quot;immune&quot; to systemic chaos. The next steps for the &lt;strong&gt;pvk&lt;/strong&gt; architecture include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Formal Verification:&lt;/strong&gt; Hardening the &quot;Geometric Poles&quot; logic with rigorous mathematical proofs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wasm Integration:&lt;/strong&gt; Bringing the Cooling Protocol to serverless environments via optimized WebAssembly targets.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Architect-Eye OS Integration:&lt;/strong&gt; Implementing Penta-V as the core stability layer for the upcoming sovereign operating system.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;join-the-architecture&quot;&gt;Join the Architecture&lt;/h3&gt;
&lt;p&gt;If you are an engineer interested in sub-200ns systems and &quot;Structural Immunity,&quot; I’d love to hear your feedback on the geometric approach.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Explore the Code:&lt;/strong&gt; [https://github.com/narukihto/Penta-V-Kernel.git].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;pypi.org:&lt;/strong&gt; [https://pypi.org/project/penta-v-kernel]&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Crates.io:&lt;/strong&gt; [https://crates.io/crates/penta_v_kernel]&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Connect:&lt;/strong&gt; &lt;em&gt;Emails are not allowed&lt;/em&gt; ** as we redefine system stability.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;&quot;Geometry is the remedy for systemic chaos.&quot;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/16363/breaking-the-200ns-barrier-the-geometric-logic-behind-penta-v-kernel</guid>
<pubDate>Sat, 02 May 2026 04:58:03 +0000</pubDate>
</item>
<item>
<title>Embedded Linux Development - Part 6</title>
<link>https://coderlegion.com/15378/embedded-linux-development-part-6</link>
<description>&lt;p&gt;In this tutorial, I will continue to discuss embedded Linux development with &lt;em&gt;ssh&lt;/em&gt; server activation. Previously, I've entered into BeagleBone Black over J1 serial header with a USB-UART converter. In here, I will show that how to enter into it with ssh command.&lt;/p&gt;
&lt;p&gt;Firstly, there are two important points that you have to do. I've already did these in previous tutorials. Firstly, I compiled the kernel with &lt;em&gt;USB Gadget&lt;/em&gt; module and secondly, I installed &lt;em&gt;openssh-server&lt;/em&gt; package into the rootfs. USB gadget module enables the USB connection as the virtual ethernet adapter.&lt;/p&gt;
&lt;p&gt;Let's do required settings for both side. Use the J1 serial header again to enter into the BeagleBone Black.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/osfzwghyeth0o8qm9j16.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;Type the &quot;root&quot; and the your password (set in previous tutorial). Also creating a normal user is good practice.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;root@can:/# useradd -m -s /bin/bash can
root@can:/# passwd can
root@can:/# usermod -aG sudo can
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I've created a user named &quot;can&quot; and set the its password. After that go to the your home directory and create a bash script. I named it as &lt;em&gt;setup-usb-gadget.sh&lt;/em&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-console&quot;&gt;root@can:/home/can# ls
setup-usb-gadget.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Open and copy this into that:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt; #!/bin/bash

INTERFACE=&quot;usb0&quot;
IP_ADDR=&quot;198.168.7.2/24&quot;

 # Load the g_ether kernel module that creates the ethernet virtual link.
modprobe g_ether

 # Configure the link interface.
ip addr flush dev &quot;$INTERFACE&quot;
ip link set &quot;$INTERFACE&quot; up
ip addr add &quot;$IP_ADDR&quot; dev &quot;$INTERFACE&quot;

 # Ensure that SSH is running.
systemctl start ssh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In here, I loaded the g_ether module into the kernel. It's the name of USB gadget module. It will create &lt;em&gt;usb0&lt;/em&gt; interface. After that I set it as &quot;up&quot; and its IP address as &quot;198.168.7.2&quot;. You can give any IP address, doesn't matter. Lastly, make sure that ssh server is enabled.&lt;/p&gt;
&lt;p&gt;Set the executable permission:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-console&quot;&gt;root@can:/home/can# chmod +x ./setup-usb-gadget.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then run it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-console&quot;&gt;root@can:/home/can# ./setup-usb-gadget.sh 
[  644.487501] g_ether gadget.0: HOST MAC f6:b0:84:e3:dc:c2
[  644.493125] g_ether gadget.0: MAC e2:87:7a:43:97:1d
[  644.498588] g_ether gadget.0: Ethernet Gadget, version: Memorial Day 2008
[  644.505565] g_ether gadget.0: g_ether ready
[  644.522283] PM: Cannot get wkup_m3_ipc handle
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lastly, list the available interface on BeagleBone Black:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-console&quot;&gt;root@can:/home/can# ip addr show
1: lo: &amp;lt;LOOPBACK,UP,LOWER_UP&amp;gt; mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host noprefixroute 
       valid_lft forever preferred_lft forever
2: sit0@NONE: &amp;lt;NOARP&amp;gt; mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/sit 0.0.0.0 brd 0.0.0.0
3: eth0: &amp;lt;BROADCAST,MULTICAST&amp;gt; mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 64:8c:bb:f2:25:78 brd ff:ff:ff:ff:ff:ff
4: usb0: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether e2:87:7a:43:97:1d brd ff:ff:ff:ff:ff:ff
    inet 198.168.7.2/24 brd 198.168.7.255 scope global usb0
       valid_lft forever preferred_lft forever
    inet6 fe80::e087:7aff:fe43:971d/64 scope link 
       valid_lft forever preferred_lft forever
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You see the &quot;usb0&quot; interface. That's enough for BeagleBone Black. Let's continue with host side. Open a new terminal and list the available interfaces on host:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-console&quot;&gt;can@can:~$ ip addr show
1: lo: &amp;lt;LOOPBACK,UP,LOWER_UP&amp;gt; mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host noprefixroute 
       valid_lft forever preferred_lft forever
2: enp3s0: &amp;lt;NO-CARRIER,BROADCAST,MULTICAST,UP&amp;gt; mtu 1500 qdisc fq_codel state DOWN group default qlen 1000
    link/ether e8:6a:64:19:45:15 brd ff:ff:ff:ff:ff:ff
3: wlp2s0: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether b0:fc:36:c1:cf:eb brd ff:ff:ff:ff:ff:ff
    inet 10.152.197.93/24 brd 10.152.197.255 scope global dynamic noprefixroute wlp2s0
       valid_lft 3521sec preferred_lft 3521sec
    inet6 fe80::18fc:91fe:ea80:8fad/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever
4: docker0: &amp;lt;NO-CARRIER,BROADCAST,MULTICAST,UP&amp;gt; mtu 1500 qdisc noqueue state DOWN group default 
    link/ether fa:47:d4:7a:67:3e brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
13: enxf6b084e3dcc2: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether f6:b0:84:e3:dc:c2 brd ff:ff:ff:ff:ff:ff
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;BeagleBone Black seems as &quot;enxf6b084e3dcc2&quot; for now. You probably see the different name. It doesn't matter. As wee did in BeagleBone Black, let's do the same things at host side:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-console&quot;&gt;can@can:~$ ip addr flush dev enxf6b084e3dcc2
can@can:~$ sudo ip link set enxf6b084e3dcc2 up
can@can:~$ sudo ip addr add 198.168.7.1/24 dev enxf6b084e3dcc2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I assigned the IP address &quot;198.168.7.1&quot; to host (I did &quot;198.168.7.2&quot; for BeagleBone Black).&lt;/p&gt;
&lt;p&gt;Lastly, ping BeagleBone Black interface:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-console&quot;&gt;can@can:~$ ping 198.168.7.2
PING 198.168.7.2 (198.168.7.2) 56(84) bytes of data.
64 bytes from 198.168.7.2: icmp_seq=1 ttl=64 time=0.904 ms
64 bytes from 198.168.7.2: icmp_seq=2 ttl=64 time=0.351 ms
64 bytes from 198.168.7.2: icmp_seq=3 ttl=64 time=0.449 ms
64 bytes from 198.168.7.2: icmp_seq=4 ttl=64 time=0.478 ms
64 bytes from 198.168.7.2: icmp_seq=5 ttl=64 time=0.585 ms
64 bytes from 198.168.7.2: icmp_seq=6 ttl=64 time=0.477 ms
^C
--- 198.168.7.2 ping statistics ---
6 packets transmitted, 6 received, 0% packet loss, time 5114ms
rtt min/avg/max/mdev = 0.351/0.540/0.904/0.176 ms
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's okey. You can connect to BeagleBone Black with ssh command for now:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-console&quot;&gt;can@can:~$ ssh can@198.168.7.2
can@198.168.7.2's password: 
Linux can 6.14.0-ge8b471285262 #1 SMP Mon Apr 13 12:31:38 +03 2026 armv7l

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Sat Jan  1 00:43:51 2000 from 198.168.7.1
can@can:~$ 
can@can:~$ ls
setup-usb-gadget.sh
can@can:~$ cd ..
can@can:/home$ cd ..
can@can:/$ ls
bin  boot  dev  etc  home  lib  lost+found  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You don't need anymore J1 serial header. You can use it within the ssh terminal for now. Also you can set the setup-usb-gadget.sh as startup script so that it runs automatically after the boot.&lt;/p&gt;
&lt;p&gt;In next tutorial, I will inspect the &lt;em&gt;uEnv.txt&lt;/em&gt; file and u-boot console. You can make many interesting tricks in there.&lt;/p&gt;
&lt;p&gt;Resources:&lt;/p&gt;
&lt;p&gt;Simmonds C., Mastering Embedded Linux Programming, Packt Publishing, 2015&lt;br&gt;
Embedded Linux system development training, Bootlin, 2024&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/15378/embedded-linux-development-part-6</guid>
<pubDate>Tue, 21 Apr 2026 09:08:36 +0000</pubDate>
</item>
<item>
<title>Embedded Linux Development - Part 5</title>
<link>https://coderlegion.com/14788/embedded-linux-development-part-5</link>
<description>&lt;p&gt;Until here, I've talked about the embedded Linux development in four stage: toolchain, bootloader, Linux kernel, rootfs. In this tutorial, I will create a SD card image including these and then run it in BeagleBone Black.&lt;/p&gt;
&lt;p&gt;Firstly, let's remember that what I've produced from these stages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;MLO&lt;/em&gt; (second-stage bootloader)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;u-boot.img&lt;/em&gt; (main bootloader)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;uEnv.txt&lt;/em&gt; (commands for the bootloader)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;zImage&lt;/em&gt; (compressed the kernel image)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;am335x-boneblack.dtb&lt;/em&gt; (device tree for the kernel)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Debian rootfs&lt;/em&gt; (including the kernel modules)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;How can I create a SD card image including all of these?&lt;/p&gt;
&lt;p&gt;Firstly, I need to create two partitions, one for booting and second for rootfs. So plugin the SD card into the PC (I'm on Ubuntu) and create the partitions:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ sudo parted -s /dev/sdX mklabel msdos # parition table and partitions

$ sudo parted -s /dev/sdX mkpart primary fat32 1MiB 65MiB # boot partition (FAT32, 64MB)
$ sudo parted -s /dev/sdX set 1 boot on # make it first parititon

$ sudo parted -s /dev/sdX mkpart primary ext4 65MiB 100% # rootfs parititon (ext4, rest of SD card)

$ sudo mkfs.vfat -F 32 -n boot /dev/sdX1 # format the boot partition
$ sudo mkfs.ext4 -L rootfs /dev/sdX2 # format the rootfs partition
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In here, I created the &quot;boot&quot; partition which was formatted with FAT32 and &quot;rootfs&quot; partition with ext4. Why did I create two distinct partitions? The first reason is that the kernel loads and runs the rootfs. So the bootloader cannot reside in the rootfs because of the bootloader runs before the kernel. Second reason is that the bootloader works with FAT32 better (even if the FAT32 size is much bigger).&lt;/p&gt;
&lt;p&gt;After that, copy the MLO, u-boot.img, uEnv.txt, zImage, am335x-boneblack.dtb into the &quot;boot&quot; partition. Also copy the your rootfs content into the &quot;rootfs&quot; partition.&lt;/p&gt;
&lt;p&gt;That's it. The SD card is ready to run the BeagleBone Black. So plugin the SD card and then power up the board. Well how to enter into the Debian rootfs? There is a J1 header besides the P9. It's the serial port. Pin 1 is GND, 4 is RX and 5 is TX. Use FT232R converter to connect these pins to PC.&lt;/p&gt;
&lt;p&gt;To see the outputs, open a terminal and the type:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ screen /dev/ttyUSBX 115200 # or /dev/ttyACMX
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reboot the BeagleBone Black and then you will see the u-boot and the kernel messages. Lastly, you will see the login session:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/koq0bpsnhesqbe5fnwsj.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;Type the &quot;root&quot; and the password (you set it in previous tutorial). That's it. You are in the BeagleBone Black terminal.&lt;/p&gt;
&lt;p&gt;You can do whatever you want after that .&lt;/p&gt;
&lt;p&gt;In the next tutorial, I will show that how to use &lt;em&gt;ssh&lt;/em&gt; command so that you can easily make connection over USB cable, not needing to J1 header.&lt;/p&gt;
&lt;p&gt;Resources:&lt;/p&gt;
&lt;p&gt;Simmonds C., Mastering Embedded Linux Programming, Packt Publishing, 2015&lt;br&gt;
Embedded Linux system development training, Bootlin, 2024&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/14788/embedded-linux-development-part-5</guid>
<pubDate>Tue, 14 Apr 2026 13:21:14 +0000</pubDate>
</item>
<item>
<title>Embedded Linux Development - Part4</title>
<link>https://coderlegion.com/14704/embedded-linux-development-part4</link>
<description>&lt;p&gt;In this tutorial, I will go with the fourth stage of embedded Linux development which is the rootfs.&lt;/p&gt;
&lt;p&gt;Every Linux-based system have a filesystem. If you are on GNU/Linux, you already probably know it and how it structured.&lt;/p&gt;
&lt;p&gt;Apart from that embedded Linux needs a few special applications to start up the system. The &lt;em&gt;init&lt;/em&gt; is the first program that runs on user-space so that its process ID always is 1. This init program is responsible for start the other user-space applications and services like shell. It generally resides at &lt;em&gt;/sbin/init&lt;/em&gt;, &lt;em&gt;/etc/init&lt;/em&gt;, &lt;em&gt;/bin/init&lt;/em&gt; or so on. The kernel try to find it and then run.&lt;/p&gt;
&lt;p&gt;The organization of a Linux rootfs in terms of directories is well-defined by the &lt;em&gt;Filesystem Hierarchy Standard&lt;/em&gt;. The general hierarchy:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;/bin&lt;/em&gt;: Basic programs&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;/boot&lt;/em&gt;: Kernel images, configurations and booting tools&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;/dev&lt;/em&gt;: Device files&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;/etc&lt;/em&gt;: System-wide configuration&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;/home&lt;/em&gt;: Directory for the users home utilities&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;/lib&lt;/em&gt;: Basic libraries&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;/media&lt;/em&gt;: Mount points for removable media&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;/mnt&lt;/em&gt;: Mount point for a temporarily mounted filesystem&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;/proc&lt;/em&gt;: Mount point for the proc virtual filesystem&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;/root&lt;/em&gt;: How directory of the root user&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;/run&lt;/em&gt;: Run-time variable data&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;/sbin&lt;/em&gt;: Basic system programs&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;/sys&lt;/em&gt;: Mount point of the sysfs virtual filesystem&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;/tmp&lt;/em&gt;: Temporary files&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;/usr&lt;/em&gt;: User-specific utilities&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;/var&lt;/em&gt;: Variable data files, for system services&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So in summary, I need to create a Linux rootfs and then put the required applications in there. I also want to build a development environment. For example, I want to run python 3 scripts, compiling C/C++ programs, or so on. But how? &lt;/p&gt;
&lt;p&gt;There are three options here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Building everything from scratch&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A minimal &lt;em&gt;BusyBox&lt;/em&gt; rootfs&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A Debian rootfs with &lt;em&gt;debootstap&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Which option is for me? &lt;/p&gt;
&lt;p&gt;Building everything from scratch means that creating the directories and then fetch the every program, package, library by hand. It means the hell of package dependency! It's not for me.&lt;/p&gt;
&lt;p&gt;A minimal rootfs can be created with BusyBox easily. It can create the classic Linux directories and put the core utilities in there like &lt;em&gt;cp&lt;/em&gt;, &lt;em&gt;mkdir&lt;/em&gt;, &lt;em&gt;ls&lt;/em&gt; or so on. If you're making embedded Linux system first time and just focusing on a &quot;running&quot; system, BusyBox is for you. But I need more. I wanna create a development environment!&lt;/p&gt;
&lt;p&gt;The right tool is debootstrap program. It creates a base Debian rootfs including the utilities that we're using everyday like &lt;em&gt;apt&lt;/em&gt;, &lt;em&gt;systemctl&lt;/em&gt;, &lt;em&gt;vi&lt;/em&gt;, or so on. This Debian rootfs is around ~200MB, while BusyBox rootfs is ~1MB.&lt;/p&gt;
&lt;p&gt;By the way, if you want to go with BusyBox rootfs (while I'm not), below is the required commands:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ git clone https://git.busybox.net/busybox
$ cd busybox
$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- defconfig
$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j$(nproc)
$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig # optional
$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- CONFIG_PREFIX=path/to/linux/rootfs install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let's focus on debootstrap. Firstly download it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ sudo apt install qemu-user-static debootstrap
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In here, I also installed &lt;em&gt;qemu-user-static&lt;/em&gt; which lets me to run the ARM binaries onto x86_64.&lt;/p&gt;
&lt;p&gt;Let's create the base Debian rootfs:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ sudo debootstrap --arch=armhf --foreign bookworm path/to/linux/rootfs http://deb.debian.org/debian
$ sudo chroot path/to/linux/rootfs /debootstrap/debootstrap --second-stage
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In here, &lt;em&gt;bookworm&lt;/em&gt; is the nickname of Debian 12 which is the last stable version. &lt;/p&gt;
&lt;p&gt;These two commands download the rootfs and then unpack it. So it takes some time (~30 minutes in my case). Don't forget, these just fetch the &lt;em&gt;required&lt;/em&gt; and &lt;em&gt;important&lt;/em&gt; packages from the Debian database, not fully OS.&lt;/p&gt;
&lt;p&gt;The rootfs is ready to be used. But there are some tricks that you always must do:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ sudo cp /usr/bin/qemu-arm-static &amp;lt;path/to/linux/rootfs&amp;gt;/usr/bin/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I also want to install the additional packages and make some settings. For example, you always must set the password. That means I need to run this rootfs on my Ubuntu host:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ sudo chroot path/to/linux/rootfs
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First thing you must is to update the password:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-console&quot;&gt;root@can:/# passwd
New password: 
Retype new password: 
passwd: password updated successfully
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Install the additional packages if you want:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-console&quot;&gt;root@can:/# apt install gcc python3 vim openssh-server # and what you need
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In here, make sure that you install the openssh-server. It's required for &lt;em&gt;ssh&lt;/em&gt; command. &lt;/p&gt;
&lt;p&gt;Lastly, I need to install the kernel modules in this rootfs. Go back to the kernel source and run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- INSTALL_MOD_PATH=path/to/linux/rootfs modules_install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's it. I created the proper Debian rootfs that I can make software development. In the next tutorial, I will use the all artifacts coming from u-boot, the kernel and the rootfs to make SD card image.&lt;/p&gt;
&lt;p&gt;Resources:&lt;/p&gt;
&lt;p&gt;Simmonds C., Mastering Embedded Linux Programming, Packt Publishing, 2015&lt;br&gt;
Embedded Linux system development training, Bootlin, 2024&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/14704/embedded-linux-development-part4</guid>
<pubDate>Mon, 13 Apr 2026 17:18:30 +0000</pubDate>
</item>
<item>
<title>Embedded Linux Development - Part 3</title>
<link>https://coderlegion.com/14684/embedded-linux-development-part-3</link>
<description>&lt;p&gt;In this tutorial, I'll go with the third stage of embedded Linux development. That's the Linux kernel.&lt;/p&gt;
&lt;p&gt;It's the responsible for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Manage all the hardware resources: CPU, memory, I/O.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Provide a set of portable, architecture and hardware independent APIs to allow user space applications and libraries to use the hardware resources.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Handle concurrent accesses and usage of hardware resources set of system calls.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The kernel provides about 400 system calls that provide the services like opening the disk file, forking the new process, managing the memory or so on. These system calls are defined in &lt;em&gt;/usr/include/x86_64-linux-gnu/asm/unistd.h&lt;/em&gt; like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt; #define __NR_read 0
 #define __NR_write 1
 #define __NR_open 2
 #define __NR_close 3
 #define __NR_stat 4
 #define __NR_fstat 5
 #define __NR_lstat 6
 #define __NR_poll 7
 #define __NR_lseek 8
 #define __NR_mmap 9
 #define __NR_mprotect 10
 #define __NR_munmap 11
 #define __NR_brk 12

(...)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In user-space applications, we generally don't use these system calls directly. These system calls are wrapped by the standard POSIX C library. If you're a programmer, you already know it.&lt;/p&gt;
&lt;p&gt;Let's start with downloading Linux kernel:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I have the 6.14 kernel (slightly older, but doesn't matter). As you see that the kernel contains thousands of device drivers, filesystem drivers, network protocols and other configurable items. &lt;/p&gt;
&lt;p&gt;As I did previously at u-boot stage, the first thing is to configure the kernel according to BeagleBone Black over configuration files (ends with &lt;em&gt;_defconfig&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;Which configuration file should be used for BeagleBone Black? It's &lt;em&gt;omap2plus_defconfig&lt;/em&gt; under linux/arch/arm/configs (OMAP is the processor family including the AM335x Sitara processor by TI).&lt;/p&gt;
&lt;p&gt;So let's configure the kernel:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- omap2plus_defconfig
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It will create the &lt;em&gt;.config&lt;/em&gt; and write the associated configs in it. &lt;/p&gt;
&lt;p&gt;As I said, this configuration file includes the generic AM335x Sitara processor configs. To customize and tailor the kernel further:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ make menuconfig # or xconfig (for Qt-based)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It will open the terminal-based GUI window to select/unselect the configurable items like:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2a6ql4jcidk6pidcck8n.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;If you're unfamiliar or newer, I DON'T recommend it. But I need to make an important configuration in here about the &lt;em&gt;USB gadget&lt;/em&gt; support. As I will explain in the next tutorail, I wanna use the &lt;em&gt;ssh&lt;/em&gt; command to make connection with BeagleBone Black. If you don't make it, you're totally free.&lt;/p&gt;
&lt;p&gt;Navigate to &lt;em&gt;Device Drivers --&amp;gt; USB Support --&amp;gt; USB Gadget Support --&amp;gt; USB Gadget precomposed configurations --&amp;gt; Ethernet Gadget (with CDC Ethernet support)&lt;/em&gt;, type &quot;M&quot; and then exit by saving. It will create the virtual USB link so that I can use the ssh command to connect to BeagleBone Black. I will explain it in the last tutorial in detail.&lt;/p&gt;
&lt;p&gt;By the way, you will see the &quot;*&quot; and &quot;M&quot; marks as you navigate the configs. &quot;*&quot; means that the configs will be embedded into the kernel image, while &quot;M&quot; means that the configs is built as modules. These are plugins that can be loaded/unloaded dynamically at runtime to the kernel.&lt;/p&gt;
&lt;p&gt;After configured the kernel, for now, you're ready to build the required outputs. &lt;/p&gt;
&lt;p&gt;Which outputs exactly should be built from the kernel source? Three things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;zImage&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;am335x-boneblack.dtb&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;kernel modules&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;zImage is the compressed the Linux kernel image. am335x-boneblack.dtb is the &lt;em&gt;device tree blob&lt;/em&gt; that describes the hardware connected to the kernel. The kernel modules also are being compiled externally, not embedded into the kernel image.&lt;/p&gt;
&lt;p&gt;In here, I have to explain the device tree concept. It's the description of the hardware that the kernel manages at runtime. As you expect, the kernel must know the hardware that it will manage. It's written generally by the processor vendor or development board engineers. The device tree corresponding to BeagleBone Black is the &lt;em&gt;am335x-boneblack.dts&lt;/em&gt; under &lt;em&gt;linux/arch/arm/boot/dts/ti/omap&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;To produces these outputs:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j$(nproc) zImage dtbs modules
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It will take for a while (~1 hours in my case). After that you will see the zImage under &lt;em&gt;linux/arch/arm/boot&lt;/em&gt; and am335x-boneblack.dtb under &lt;em&gt;linux/arch/arm/boot/dts/ti/omap&lt;/em&gt;. &lt;/p&gt;
&lt;p&gt;The kernel modules are compiled ending with &lt;em&gt;.ko&lt;/em&gt; suffix. After the rootfs is being created, I will install these kernel modules in it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- INSTALL_MOD_PATH=path/to/linux/rootfs modules_install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But DON'T do it for now. I need to create a rootfs firstly and then install the kernel modules in there. I will show it in the next tutorial.&lt;/p&gt;
&lt;p&gt;That's it. I've produced the three essential components from the kernel source: zImage, am335x-boneblack.dtb, and kernel modules. In the next tutorial, I will interest with the rootfs.&lt;/p&gt;
&lt;p&gt;Resources:&lt;/p&gt;
&lt;p&gt;Simmonds C., Mastering Embedded Linux Programming, Packt Publishing, 2015&lt;br&gt;
Embedded Linux system development training, Bootlin, 2024&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/14684/embedded-linux-development-part-3</guid>
<pubDate>Mon, 13 Apr 2026 14:06:30 +0000</pubDate>
</item>
<item>
<title>Embedded Linux Development - Part 2</title>
<link>https://coderlegion.com/14603/embedded-linux-development-part-2</link>
<description>&lt;p&gt;In this tutorial, I will continue the embedded Linux development series with second stage which is the bootloader.&lt;/p&gt;
&lt;p&gt;Firstly, a bit theoretical background.&lt;/p&gt;
&lt;p&gt;The bootloader is a piece of code responsible for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Basic hardware initialization&lt;/li&gt;
&lt;li&gt;Loading of an application binary, usually an operating&lt;br&gt;
system kernel, from flash storage, from the network, or from another type of non-volatile storage&lt;/li&gt;
&lt;li&gt;Possible decompression of the application binary&lt;/li&gt;
&lt;li&gt;Execution of the application&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Besides these basic functions, most bootloaders provide a shell or menu:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Menu to select the operating system to load&lt;/li&gt;
&lt;li&gt;Shell with commands to load data from storage or network, inspect memory, perform hardware testing/diagnostics&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Boot process highly is depended to processor. Let's focus on AM335x Sitara (ARM) booting sequence. &lt;/p&gt;
&lt;p&gt;Most embedded processors include a &lt;em&gt;ROM code&lt;/em&gt; that implements the initial step of the boot process. It's called the &lt;em&gt;first-stage loader&lt;/em&gt;. This ROM code is written by the processor vendor directly built into the processor. That means, it cannot be changed or updated by the others. It initializes the CPU and internal SRAM. It also searches the &lt;em&gt;MLO&lt;/em&gt; from NAND/NOR flash, from USB, from SD card, from eMMC, etc.&lt;/p&gt;
&lt;p&gt;MLO is a file that you need to produce from the bootloader source code. It's called also &lt;em&gt;second-program loader (SPL)&lt;/em&gt;. Its mission is to initialize the DDR RAM and run the main bootloader.&lt;/p&gt;
&lt;p&gt;The main bootloader initializes the hardware and then loads the Linux kernel and then jump to it. I will use the &lt;em&gt;U-Boot&lt;/em&gt;. It's the mostly used bootloader in embedded world.&lt;/p&gt;
&lt;p&gt;In summary, the booting process: &lt;em&gt;ROM code --&amp;gt; MLO (SPL) --&amp;gt; U-Boot --&amp;gt; Linux kernel&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;So after compiled the u-boot, I expect these:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;MLO&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;u-boot.img&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let's produce these.&lt;/p&gt;
&lt;p&gt;Firstly, download the u-boot:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ git clone https://source.denx.de/u-boot/u-boot.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you look at the source code, you will see that the u-boot supports a lot of processors and boards. So first thing you should do is to configure the u-boot over configuration files (ends with _*_defconfig_) under &lt;em&gt;u-boot/configs&lt;/em&gt;. &lt;/p&gt;
&lt;p&gt;But which exactly configuration file corresponds to BeagleBone Black? The answer is _am335x_evm_defconfig_. It's generic AM335x Sitara processor configuration file.&lt;/p&gt;
&lt;p&gt;To use this configuration file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ make am335x_evm_defconfig
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It will create the &lt;em&gt;.config&lt;/em&gt; file and write the configs in it. The important point is that you never edit this file by hand.&lt;/p&gt;
&lt;p&gt;To customize and tailor this configs further:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ make menuconfig     # or xconfig (for Qt-based)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It will open the terminal-based GUI to select/unselect the configs. If you are unfamiliar or newer, you should NOT do it. Because it will show the tons of different configs and it can seem messy. The previous command already made the generic required configs.&lt;/p&gt;
&lt;p&gt;After setting up the configs, let's build the outputs.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It will take for a while. Because it will compile the tons of source file and then link them together. The output will be like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;(...)

  AR      spl/fs/fat/built-in.o
  CC      spl/fs/fs_internal.o
  AR      spl/fs/built-in.o
  CC      spl/net/arp.o
  CC      spl/net/bootp.o
  CC      spl/net/eth-uclass.o
  CC      spl/net/eth_common.o
  CC      spl/net/net.o
  CC      spl/net/ping.o
  CC      spl/net/tftp.o
  AR      spl/net/built-in.o
  LDS     spl/u-boot-spl.lds
  LD      spl/u-boot-spl
  OBJCOPY spl/u-boot-spl-nodtb.bin
  COPY    spl/u-boot-spl.bin
  MKIMAGE MLO
  MKIMAGE MLO.byteswap
  SYM     spl/u-boot-spl.sym
  MKIMAGE u-boot-dtb.img
  OFCHK   .config
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After that you can see the u-boot/MLO, u-boot/u-boot.img and a lots of other files. &lt;/p&gt;
&lt;p&gt;As I said before, I need the MLO and u-boot.img and will use these in further. For now, just keeps these in mind.&lt;/p&gt;
&lt;p&gt;There is also one more important file that I need. It's &lt;em&gt;uEnv.txt&lt;/em&gt; and used by the u-boot when loading the Linux kernel. It consists of the key-value pairs that tell some command to u-boot. So create a new file named of it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-console&quot;&gt;$ vim uEnv.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And write the following commands in it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-console&quot;&gt;bootpart=0:1
devtype=mmc
bootdir=
bootfile=zImage
bootpartition=mmcblk0p2
console=ttyS0,115200n8
loadaddr=0x82000000
fdtaddr=0x88000000
set_mmc1=if test $board_name = A33515BB; then setenv bootpartition mmcblk1p2; fi
set_bootargs=setenv bootargs console=${console} root=/dev/${bootpartition} rw rootfstype=ext4 rootwait
uenvcmd=run set_mmc1; run set_bootargs;run loadimage;run loadfdt;printenv bootargs;bootz ${loadaddr} - ${fdtaddr}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At the end of this series, you will fully understand what these are. But basically:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;the console name is set to ttyS0 and baud rate to 115200.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;the Linux kernel name is defined as &lt;em&gt;zImage&lt;/em&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;the address that Linux kernel image will be placed is set to 0x82000000 and the address that device tree blob will be placed to 0x88000000.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;set the boot environment and load the Linux kernel with &lt;em&gt;bootz&lt;/em&gt; command.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Save it and then keep this file in mind as being MLO and u-boot.img.&lt;/p&gt;
&lt;p&gt;That's it. You've produced the three components related to bootloader stage. In next tutorial, I will interest with Linux kernel itself.&lt;/p&gt;
&lt;p&gt;Resources:&lt;/p&gt;
&lt;p&gt;Simmonds C., Mastering Embedded Linux Programming, Packt Publishing, 2015&lt;br&gt;
Embedded Linux system development training, Bootlin, 2024&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/14603/embedded-linux-development-part-2</guid>
<pubDate>Sun, 12 Apr 2026 19:13:05 +0000</pubDate>
</item>
<item>
<title>Embedded Linux Development - Part 1</title>
<link>https://coderlegion.com/14561/embedded-linux-development-part-1</link>
<description>&lt;p&gt;In today, I'm beginning a new series about embedded Linux development. I'll show that how to build an end-to-end embedded Linux system from scratch by hand.&lt;/p&gt;
&lt;p&gt;I'll fetch the associated every stuffs from source code and then build the corresponding outputs.&lt;/p&gt;
&lt;p&gt;I will target the &lt;strong&gt;BeagleBone Black Rev C&lt;/strong&gt; board. I will compile the everything for that board. So you need the following things for this series:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;BeagleBone Black Rev C&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;USB Mini-B to USB-A cable&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;FTDI FT232R converter&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SD card&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Overall, embedded Linux development consists of four components:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Toolchain&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Bootloader&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Linux kernel&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Rootfs&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this tutorial, I will explain the toolchain that I need for all development sequence.&lt;/p&gt;
&lt;p&gt;The toolchain is the first element of embedded Linux and the starting point of the project.&lt;/p&gt;
&lt;p&gt;A toolchain is the set of tools that compiles source code into executables that can run on your target device, and includes many binaries for the different purposes. Usually, toolchains for Linux are based on components from the GNU project. A standard GNU toolchain consists of three main components:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Binutils&lt;/em&gt; is a set of tools to generate and manipulate binaries for a given architecture. Common binaries: &lt;em&gt;as&lt;/em&gt;, &lt;em&gt;ld&lt;/em&gt;, &lt;em&gt;ar&lt;/em&gt;, &lt;em&gt;randlib&lt;/em&gt;, &lt;em&gt;objdump&lt;/em&gt;, &lt;em&gt;readelf&lt;/em&gt;, &lt;em&gt;size&lt;/em&gt;, &lt;em&gt;nm&lt;/em&gt;, &lt;em&gt;strings&lt;/em&gt;, &lt;em&gt;strip&lt;/em&gt;, etc.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;GCC&lt;/em&gt; is the main compiler.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;C library&lt;/em&gt; is used to interract with the kernel. The programming interface to the UNIX operating system is defined in the C language, which is defined by the POSIX standards. The C library is the implementation of the interface.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In here, there are two types of toolchain:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Native&lt;/em&gt;: This toolchain runs on the same type of system.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Cross&lt;/em&gt;: This toolchain run on a different type of system than the target.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Almost all embedded Linux development is done using a cross development toolchain. Because doing embedded development, there is always a split between:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The &lt;em&gt;host&lt;/em&gt;, the development workstation&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;em&gt;target&lt;/em&gt;, which is the embedded system&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;They are connected by various means: almost always a serial line for debugging purposes, frequently a network connection, sometimes a JTAG interface for low-level stuffs.&lt;/p&gt;
&lt;p&gt;Many UNIX/Linux toolchains rely on architecture tuple names to identify machines. You probably compiled many programs on your host already and used the &lt;em&gt;gcc&lt;/em&gt; command. But it's actually a shorthand. You can exactly see it with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-console&quot;&gt;$ gcc -dumpmachine
x86_64-linux-gnu
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Other examples: &lt;em&gt;arm-linux-gnueabihf&lt;/em&gt;, &lt;em&gt;mips64el-linux-gnu&lt;/em&gt;, &lt;em&gt;arm-none-eabi-&lt;/em&gt; etc.&lt;/p&gt;
&lt;p&gt;These tuples are 3 or 4 parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The architecture name: &lt;em&gt;arm&lt;/em&gt;, &lt;em&gt;riscv&lt;/em&gt;, &lt;em&gt;mips64el&lt;/em&gt;, etc.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Optionally, a vendor name, which is free-form string.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An operating system name, or &lt;em&gt;none&lt;/em&gt; when not targeting an operating system.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Of course, these are the pre-built toolchains. If you want, you can also create your own toolchain with &lt;em&gt;crosstool-ng&lt;/em&gt;. It's another tutorial topic. But shortly, when building a toolchain, the ABI used to generate binaries needs to be defined. ABI, &lt;em&gt;Application Binary Interface&lt;/em&gt;, defines the calling conventions (how function arguments are passed, how the return value is passed, how system calls are made or so on) and the organization of structures. All binaries in a system are typically compiled with the same ABI, and the kernel must understand this ABI.&lt;/p&gt;
&lt;p&gt;Anyway, I gave you the enough theoretical toolchain background. Let's go with our concern.&lt;/p&gt;
&lt;p&gt;Which toolchain should be used for BeagleBone Black?&lt;/p&gt;
&lt;p&gt;You have to know the used ARM processor in here. BeageBone Black has AM335x Sitara processor which is based on ARM-A8 (ARMv7), 32-bit architecture. So the toolchain for that is the &lt;em&gt;gcc-arm-linux-gnueabihf&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The first thing in implementing embedded Linux is to install it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ sudo apt install gcc-arm-linux-gnueabihf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After that validate it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-console&quot;&gt;$ arm-linux-gnueabihf-gcc --version
arm-linux-gnueabihf-gcc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0
Copyright (C) 2023 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's it. You have the required toolchain and ready to go with the bootloader.&lt;/p&gt;
&lt;p&gt;Resources:&lt;/p&gt;
&lt;p&gt;Simmonds C., Mastering Embedded Linux Programming, Packt Publishing, 2015&lt;br&gt;
Embedded Linux system development training, Bootlin, 2024&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/14561/embedded-linux-development-part-1</guid>
<pubDate>Sun, 12 Apr 2026 12:59:13 +0000</pubDate>
</item>
<item>
<title>POSIX Shared Memory</title>
<link>https://coderlegion.com/14234/posix-shared-memory</link>
<description>&lt;p&gt;POSIX shared memory allows to us to share a mapped region between unrelated processes without needing to create a corresponding  file.&lt;/p&gt;
&lt;p&gt;Linux uses a dedicated &lt;code&gt;tmpfs&lt;/code&gt; file system mounted under the directory &lt;code&gt;/dev/shm&lt;/code&gt;. This file system has kernel persistance - the shared memory objects that it contains will persists even if no process currently has them open, but they will be lost if the system is shut down.&lt;/p&gt;
&lt;p&gt;To use a POSIX shared memory object, we perform two steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Use the &lt;code&gt;shm_open()&lt;/code&gt; function to open an object with a specified name. The &lt;code&gt;shm_open()&lt;/code&gt; function is analogous to the &lt;code&gt;open()&lt;/code&gt; system call. It either creates a new shared memory object or opens an existing object. As its function result, &lt;code&gt;shm_open()&lt;/code&gt; returns a file descriptor referring to the object.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pass the file descriptor obtained in the previous step in a call to &lt;code&gt;mmap()&lt;/code&gt; that specifies &lt;code&gt;MAP_SHARED&lt;/code&gt; in the flags argument. This maps the shared memory object into the process's virtual address space.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt; #include &amp;lt;sys/mman.h&amp;gt;
 #include &amp;lt;sys/stat.h&amp;gt;        /* For mode constants */
 #include &amp;lt;fcntl.h&amp;gt;           /* For O_* constants */

int shm_open(const char *name, int oflag, mode_t mode);
int shm_unlink(const char *name);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;name&lt;/code&gt; argument identifies the shared memory object to be created or opened. The &lt;code&gt;oflags&lt;/code&gt; argument is a mask of bits of O_* as being in &lt;code&gt;open()&lt;/code&gt; system call.&lt;/p&gt;
&lt;p&gt;At any point, we can apply &lt;code&gt;fstat()&lt;/code&gt; to the file descriptor returned by &lt;code&gt;shm_open()&lt;/code&gt; in order to obtain a &lt;code&gt;stat&lt;/code&gt; structure whose contain information about the shared memory object.&lt;/p&gt;
&lt;p&gt;Example of creating a POSIX shared memory is &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/CanGulmez/Linux-Programming-Interface/blob/main/pshm/pshm_create.c&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;After created the memory object, we of course want to read and write the data into that. Data reading program is &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/CanGulmez/Linux-Programming-Interface/blob/main/pshm/pshm_write.c&quot;&gt;here&lt;/a&gt; and writing program is &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/CanGulmez/Linux-Programming-Interface/blob/main/pshm/pshm_read.c&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;SUSv3 requires that POSIX shared memory objects have at least kernel persistence; that is, they continue to exist until they are explicitly removed or the system is rebooted. When a shared memory object is no longer required, it should be removed using &lt;code&gt;shm_unlink()&lt;/code&gt;. The example program of its usage is &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/CanGulmez/Linux-Programming-Interface/blob/main/pshm/pshm_unlink.c&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In summary, a POSIX shared memory object is used to share a region of memory between unrelated processes without creating an underlying disk file. To do this, we replace the call to &lt;code&gt;open()&lt;/code&gt; that normally precedes &lt;code&gt;mmap()&lt;/code&gt; with a call to &lt;code&gt;shm_open()&lt;/code&gt;. The &lt;code&gt;shm_open()&lt;/code&gt; call creates a file in a memory-based file system, and we can employ traditional file descriptor system calls to perform various operations on this virtual file. In particular, &lt;code&gt;ftruncate()&lt;/code&gt; must be used to set the size of the shared memory object, since initially it has a length of zero.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/14234/posix-shared-memory</guid>
<pubDate>Wed, 08 Apr 2026 12:50:12 +0000</pubDate>
</item>
<item>
<title>The Linux Programming Interface - Time</title>
<link>https://coderlegion.com/14225/the-linux-programming-interface-time</link>
<description>&lt;p&gt;In any type of program, we mostly are interested in two different time:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Real (Calendar) time&lt;/em&gt;: This time is measured from a standard point (generally, UTC, 1 January 1970). Obtaining the calendar time is primarily source of what you see at screen of your computer. &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Process time&lt;/em&gt;: This is the amount of CPU time used by a process. Measuring process time is useful for checking or optimizing the performance of a program or algorithm.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Every computer has a dedicated built-in hardware clock that enables the kernel to measure real and process time.&lt;/p&gt;
&lt;h3 id=&quot;calendar-time-and-time-conversion&quot;&gt;Calendar Time and Time Conversion&lt;/h3&gt;
&lt;p&gt;To handle real time, the GNU libraries give us many functions as below:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uz9b8cmsnoijig1yfp2w.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;These are also function signatures that we use when handling time in the program:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;time_t time(time_t *tloc);
char *asctime(const struct tm *tm);
char *ctime(const time_t *timep);
struct tm *gmtime(const time_t *timep);
struct tm *localtime(const time_t *timep);
time_t mktime(struct tm *tm);
size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;em&gt;time()&lt;/em&gt; system call returns the number of seconds since the Epoch (1 January 1970) and then this number of seconds are stored in _time_t_ (&lt;code&gt;typedef long time_t&lt;/code&gt;) data type.&lt;/p&gt;
&lt;p&gt;To convert this number of seconds in printable format, we use the &lt;em&gt;ctime()&lt;/em&gt; function.&lt;/p&gt;
&lt;p&gt;We can also convert this number of seconds into a so-called &lt;em&gt;broken-down time&lt;/em&gt; (&lt;code&gt;struct tm&lt;/code&gt;) by using &lt;em&gt;gmtime()&lt;/em&gt; and &lt;em&gt;localtime()&lt;/em&gt; functions. After that, we see the year, month, week, hours and so on. &lt;/p&gt;
&lt;p&gt;The &lt;em&gt;mktime()&lt;/em&gt; function does vise-verse, translates a broken-down time into number of seconds.&lt;/p&gt;
&lt;p&gt;We also use &lt;em&gt;asctime()&lt;/em&gt; to convert broken-down time to printable format.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gcxbadf92n1v4e4sf0fr.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;I put here a simple program that shows the how to use these functions I've described:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; /* Retrieving and converting calendar times */

 #include &quot;../linux.h&quot;

 #define SECONDS_IN_TROPICAL_YEAR    (365.24219 * 24 * 60 * 60)

void main(int argc, char *argv[])
{
   time_t t;
   struct tm *gmp, *locp;
   struct tm gm, loc;
   struct timeval tv;

   t = time(NULL);
   printf(&quot;Seconds since the Epoch: %ld&quot;, (long) t);
   printf(&quot; (about %6.3f years)\n&quot;, t / SECONDS_IN_TROPICAL_YEAR);

   if (gettimeofday(&amp;amp;tv, NULL) == -1)
      syscall_error();
   printf(&quot; gettimeofday() returned %ld secs, %ld microsecs\n&quot;,
      (long) tv.tv_sec, (long) tv.tv_usec);

   gmp = gmtime(&amp;amp;t);
   if (gmp == NULL)
      syscall_error();

   gm = *gmp;  /* Save local copy */

   printf(&quot;Broken down by gmtime():\n&quot;);
   printf(&quot; year=%d, mon=%d, mday=%d, hour=%d, min=%d, sec=%d &quot;, 
      gm.tm_year, gm.tm_mon, gm.tm_mday, gm.tm_hour, gm.tm_min, gm.tm_sec);
   printf(&quot; wday=%d, yday=%d, isdst=%d\n&quot;, gm.tm_wday, gm.tm_yday, gm.tm_isdst);

   locp = localtime(&amp;amp;t);
   if (locp == NULL)
      syscall_error();

   loc = *locp;   /* Save local copy */

   printf(&quot;Broken down by localtime():\n&quot;);
   printf(&quot; year=%d, mon=%d, mday=%d, hour=%d, min=%d, sec=%d &quot;, 
      loc.tm_year, loc.tm_mon, loc.tm_mday, loc.tm_hour, loc.tm_min, loc.tm_sec);
   printf(&quot; wday=%d, yday=%d, isdst=%d\n&quot;, loc.tm_wday, loc.tm_yday, loc.tm_isdst);

   printf(&quot;asctime() formats the gmtime() value as: %s\n&quot;, asctime(&amp;amp;gm));
   printf(&quot;ctime() formats the time() value as: %s\n&quot;, ctime(&amp;amp;t));

   printf(&quot;mktime() of gmtime() value: %ld secs\n&quot;, (long) mktime(&amp;amp;gm));
   printf(&quot;mktime() of localtime() value: %ld secs\n&quot;, (long) mktime(&amp;amp;loc));
   
   exit(EXIT_SUCCESS);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;em&gt;strftime()&lt;/em&gt; function provides us with more precise control when converting a broken-down into printable form. We just put pre-defined specifiers into third argument of &lt;em&gt;strftime()&lt;/em&gt; function. These specifiers:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ioxot3d49kl9b2wat9qh.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;Below is the another program that shows the function usage:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; /* A function that returns a string containing the current time */

 #include &quot;../linux.h&quot;

 #define BUF_SIZE  1000

/* Return a string containing the current time formatted according to
   the specification in 'format' (see strftime(3) for specifiers).
   If 'format' is NULL, we use &quot;%c&quot; as a specifier (which gives the
   date and time as for ctime(3), but without the trailing newline).
   Returns NULL on error. */

char *currTime(const char *format)
{
   static char buffer[BUF_SIZE];
   time_t t;
   size_t s;
   struct tm *tm;

   t = time(NULL);
   tm = localtime(&amp;amp;t); 
   if (tm == NULL)
      return NULL;

   s = strftime(buffer, BUF_SIZE, (format != NULL) ? format : &quot;%c&quot;, tm);
   
   return (s == 0) ? NULL : buffer;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;timezones&quot;&gt;Timezones&lt;/h3&gt;
&lt;p&gt;Different countries operate on different timezones and DST regimes. Programs that input and output times must take care of this.&lt;/p&gt;
&lt;p&gt;Timezone information tends to be both voluminous and volatile. So rather than encoding it directly into libraries, the system maintains this information in files under &lt;em&gt;/usr/share/zoneinfo&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;To specify a timezone when running a program, we set the &lt;code&gt;TZ&lt;/code&gt; environment variable to a string consisting of a colon followed by one of the timezone names (like TZ=&quot;:Pacific/Auckland&quot;).&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;tzset()&lt;/em&gt; function is used to obtain the current timezone setting. This function initializes three global variables: &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;char *tzname[2];
int daylight;
long timezone;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This function first checks the &lt;code&gt;TZ&lt;/code&gt; environment variable. If this variable is not set, then the timezone is initialized to the default defined in the timezone file &lt;em&gt;/etc/localtime&lt;/em&gt;. If the &lt;code&gt;TZ&lt;/code&gt; environment variable is defined with a value that can't be matched to a timezone file, or it is an empty string, then UTC is used.&lt;/p&gt;
&lt;h3 id=&quot;locales&quot;&gt;Locales&lt;/h3&gt;
&lt;p&gt;Different countries use different conventions for displaying information such as numbers, currency amounts, dates, and times.&lt;/p&gt;
&lt;p&gt;Ideally, all programs designed to run in more than one location should deal with locales in order to display and input information in the user's preferred language and format. This constitutes the complex subject of &lt;em&gt;internationalization (I18N)&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Like timezone information, locales are maintained under &lt;em&gt;/usr/share/locale&lt;/em&gt;. Under each locale subdirectory is a standard set of files that specify the conventions for this locale:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wfysdu255ptyie3luirz.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;setlocale()&lt;/em&gt; function is used to both set and query program's current locale. Below is the its function signature:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;char *setlocale(int category, const char *locale);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are two different methods of setting the locale. The &lt;em&gt;locale&lt;/em&gt; argument may be a string specifying one of the locales defined under &lt;em&gt;/usr/lib/locale&lt;/em&gt; or more conveniently &lt;code&gt;selocale(LC_ALL, &quot;&quot;)&lt;/code&gt; meaning that locale settings should be taken from environment variables.&lt;/p&gt;
&lt;h3 id=&quot;process-time&quot;&gt;Process Time&lt;/h3&gt;
&lt;p&gt;Process time is the amount of CPU time used by a process since it was created. For recording purposes, the kernel separates CPU time into the following two components:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;User CPU time&lt;/em&gt; (amount of time spent executing in user mode)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;System CPU time&lt;/em&gt; (amount of time spent executing in kernel mode)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sometimes, we refer to process time as the &lt;em&gt;total CPU time&lt;/em&gt; consumed by the process.&lt;/p&gt;
&lt;p&gt;When dealing with process time, &lt;em&gt;times()&lt;/em&gt; and &lt;em&gt;clock()&lt;/em&gt; functions are used. Signatures are below:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;clock_t times(struct tms *buf);
clock_t clock(void);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;em&gt;times()&lt;/em&gt; system call retrieves process time information (both user and system time separately) and store it into &lt;code&gt;struct tms&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Otherwise, &lt;em&gt;clock()&lt;/em&gt; function is simpler and returns the total amount of CPU time. Below is the basic program that shows both of these functions.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; /* Retrieving process CPU times */

 #include &quot;../linux.h&quot;

void displayProcessTimes(const char *msg)
{
   struct tms t;
   clock_t clockTime;
   static long clockTicks = 0;
   
   if (msg != NULL)
      printf(&quot;%s&quot;, msg);

   if (clockTicks == 0) {  /* Fetch clock ticks on first call */
      clockTicks = sysconf(_SC_CLK_TCK);
      if (clockTicks == -1)
         syscall_error();
   }

   clockTime = clock();
   if (clockTime == -1)
      syscall_error();

   printf(&quot;clock() returns: %ld clocks-per-sec (%.2f secs)\n&quot;,
      (long) clockTime, (double) clockTime / CLOCKS_PER_SEC);

   if (times(&amp;amp;t) == -1)
      syscall_error();

   printf(&quot;times() yields: user CPU: %.2f; system CPU: %.2f\n&quot;, 
      (double) t.tms_utime / clockTicks, 
      (double) t.tms_stime / clockTicks);
}

void main(int argc, char *argv[])
{
   int numCalls, i;

   printf(&quot;CLOCKS_PER_SEC: %ld  sysconf(_SC_CLK_TCK): %ld\n\n&quot;,
      (long) CLOCKS_PER_SEC, sysconf(_SC_CLK_TCK));

   displayProcessTimes(&quot;At program start:\n&quot;);

   numCalls = (argc &amp;gt; 1) ? atoi(argv[1]) : 100000000;
   for (i = 0; i &amp;lt; numCalls; i++)
      (void) getppid();

   displayProcessTimes(&quot;After getppid() loop:\n&quot;);

   exit(EXIT_SUCCESS);
}
&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/14225/the-linux-programming-interface-time</guid>
<pubDate>Wed, 08 Apr 2026 10:43:35 +0000</pubDate>
</item>
<item>
<title>ELF Executable Analysis in Detail</title>
<link>https://coderlegion.com/14149/elf-executable-analysis-in-detail</link>
<description>&lt;p&gt;In everyday, we run some kind of programs to handle our works. There are many type of programs, GUIs, CLIs, TUIs or so on. But at low level, there are two kind of program formats: PE (Portable Executable) for Windows and ELF (Executable and Linkable Format) for Linux. &lt;/p&gt;
&lt;p&gt;In this tutorial, I will explain the ELF executables in detail.&lt;/p&gt;
&lt;p&gt;Firstly, let's start with the overall layout:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cpujvtc64w38ocjvh6zc.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;As you see, an ELF executable consists of four layers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Executable Header&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Program Headers&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sections&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Section Headers&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;executable-header&quot;&gt;Executable Header&lt;/h4&gt;
&lt;p&gt;Every ELF file starts with an &lt;em&gt;executable header&lt;/em&gt;, which is just a structured series of bytes telling you that it's an ELF file, what kind of ELF file it is, and where in the file to find all the other contents. It's defined as follow in /usr/include/elf.h:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/durlu5pmfkinz7utvied.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;In here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;e_ident: The executable header starts with a 16-byte array. First 4-byte, magic value, identifying the file as an ELF binary.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;e_type: The type of the executable. For example REL means relocatable object file, EXEC means executable binary and DYN means dynamic libraries.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;e_machine: The architecture that the executable is intended to run on.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;e_version: The version of the ELF specification (always 1, I really don't know why it exists ).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;r_entry: The virtual address at which execution should start.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;e_phoff, e_shoff: The file offsets to the beginning of the program header table and the section header table.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;e_flags: The flags specific to the architecture for which the binary is compiled (typically 0 for x86_64).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;e_ehsize: The size of the executable header, in bytes (for 64-bit systems, it's always 64).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can see the executable header with this command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ readelf -h ./prog
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              DYN (Position-Independent Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x1400
  Start of program headers:          64 (bytes into file)
  Start of section headers:          135496 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         13
  Size of section headers:           64 (bytes)
  Number of section headers:         29
  Section header string table index: 28
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;section-headers&quot;&gt;Section Headers&lt;/h4&gt;
&lt;p&gt;The code and data in an ELF binary are logically divided into continuous nonoverlapping chunks called &lt;strong&gt;section&lt;/strong&gt;s. Sections don't have any predetermined structure; instead, the structure of each section varies depending on the contents. So every section is described in &lt;em&gt;section header table&lt;/em&gt;. It's defined as follow:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/grorlbmbp2grio4ulyfa.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;In here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;sh_name: The index of the sections.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;sh_type: Every section has a type, indicated by an integer field. Common types: PROBITS for machine instructions, SYMTAB for static symbol table, DYNSYM for dynamic symbol table, STRTAB for string tables, REL(A) for relocation entries used by the linker, DYNAMIC for information needed for dynamic linking or so on.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;sh_flags: The section flags. Common flags: W means the section is writable, A means the content of the section are to be loaded into memory when executing the executable, X means the section is executable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;sh_addr, sh_offset, sh_size: The virtual address, file offset (in bytes from the start&lt;br&gt;
of the file), and size (in bytes) of the sections.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;sh_link: Sometimes, there are relationships between sections. This field keeps the index count of the related sections.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;sh_info: Additional information about sections.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;sh_addralign: Some sections may need to be aligned in memory in a particular way for efficiency of memory accesses. The values 0 and 1 are reserved to indicate no special alignment needs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;sh_entsize: Some sections contain a table of well-defined data structures. For such sections, this field indicates the size in bytes of each entry in the table. When the field is unused, it is set to zero.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can see the section headers with this command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ readelf --sections --wide ./prog
There are 29 section headers, starting at offset 0x21148:

Section Headers:
  [Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            0000000000000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        0000000000000318 000318 00001c 00   A  0   0  1
  [ 2] .note.gnu.property NOTE            0000000000000338 000338 000030 00   A  0   0  8
  [ 3] .note.gnu.build-id NOTE            0000000000000368 000368 000024 00   A  0   0  4
  [ 4] .note.ABI-tag     NOTE            000000000000038c 00038c 000020 00   A  0   0  4
  [ 5] .gnu.hash         GNU_HASH        00000000000003b0 0003b0 000028 00   A  6   0  8
  [ 6] .dynsym           DYNSYM          00000000000003d8 0003d8 000378 18   A  7   1  8
  [ 7] .dynstr           STRTAB          0000000000000750 000750 00016d 00   A  0   0  1
  [ 8] .gnu.version      VERSYM          00000000000008be 0008be 00004a 02   A  6   0  2
  [ 9] .gnu.version_r    VERNEED         0000000000000908 000908 000080 00   A  7   2  8
  [10] .rela.dyn         RELA            0000000000000988 000988 0000d8 18   A  6   0  8
  [11] .rela.plt         RELA            0000000000000a60 000a60 0002d0 18  AI  6  24  8
  [12] .init             PROGBITS        0000000000001000 001000 00001b 00  AX  0   0  4
  [13] .plt              PROGBITS        0000000000001020 001020 0001f0 10  AX  0   0 16
  [14] .plt.got          PROGBITS        0000000000001210 001210 000010 10  AX  0   0 16
  [15] .plt.sec          PROGBITS        0000000000001220 001220 0001e0 10  AX  0   0 16
  [16] .text             PROGBITS        0000000000001400 001400 018c54 00  AX  0   0 16
  [17] .fini             PROGBITS        000000000001a054 01a054 00000d 00  AX  0   0  4
  [18] .rodata           PROGBITS        000000000001b000 01b000 002ee8 00   A  0   0 16
  [19] .eh_frame_hdr     PROGBITS        000000000001dee8 01dee8 0005fc 00   A  0   0  4
  [20] .eh_frame         PROGBITS        000000000001e4e8 01e4e8 001914 00   A  0   0  8
  [21] .init_array       INIT_ARRAY      0000000000020cc0 020cc0 000008 08  WA  0   0  8
  [22] .fini_array       FINI_ARRAY      0000000000020cc8 020cc8 000008 08  WA  0   0  8
  [23] .dynamic          DYNAMIC         0000000000020cd0 020cd0 000200 10  WA  7   0  8
  [24] .got              PROGBITS        0000000000020ed0 020ed0 000130 08  WA  0   0  8
  [25] .data             PROGBITS        0000000000021000 021000 000010 00  WA  0   0  8
  [26] .bss              NOBITS          0000000000021020 021010 000010 00  WA  0   0 32
  [27] .comment          PROGBITS        0000000000000000 021010 00002b 01  MS  0   0  1
  [28] .shstrtab         STRTAB          0000000000000000 02103b 00010a 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  D (mbind), l (large), p (processor specific)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;sections&quot;&gt;Sections&lt;/h4&gt;
&lt;p&gt;In previous chapter, you see that the ELF executable has a set of sections at which each section has a special meaning. For now, let me explain these:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;.init and .fini: The &lt;em&gt;.init&lt;/em&gt; section contains machine code that performs initialization before the &lt;em&gt;main()&lt;/em&gt; function and &lt;em&gt;.fini&lt;/em&gt; runs after it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;.text: This section is where the main code of the executable resides that we've wrote. It's the main concern for binary analysis or reverse engineering efforts.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;.bss, .data, .rodata: There sections are where the program variables live. According to the declaration style, the variable goes into one of the three sections. For example, &lt;em&gt;initialized&lt;/em&gt; global variables live in &lt;em&gt;.data&lt;/em&gt;, &lt;em&gt;uninitialized&lt;/em&gt; global variables live in &lt;em&gt;.bss&lt;/em&gt; and &lt;em&gt;constant&lt;/em&gt; variables live in &lt;em&gt;.rodata&lt;/em&gt;. Don't forget that the local variables go in the stack, so &lt;em&gt;.text&lt;/em&gt;, not into one of these sections.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;.rel.&lt;em&gt; and .rela.&lt;/em&gt;: These sections contain the information used by the linker for performing relocations.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;.dynamic: It contains the &quot;road map&quot; for dynamic linker when loading and setting up the ELF executable.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As an example, you can inspect a specific section with this command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ objdump -j .text -d ./prog

./prog:     file format elf64-x86-64

Disassembly of section .text:

0000000000001400 &amp;lt;.text&amp;gt;:
    1400:       f3 0f 1e fa             endbr64
    1404:       31 ed                   xor    %ebp,%ebp
    1406:       49 89 d1                mov    %rdx,%r9
    1409:       5e                      pop    %rsi
    140a:       48 89 e2                mov    %rsp,%rdx
    140d:       48 83 e4 f0             and    $0xfffffffffffffff0,%rsp
    1411:       50                      push   %rax
    1412:       54                      push   %rsp
    1413:       45 31 c0                xor    %r8d,%r8d
    1416:       31 c9                   xor    %ecx,%ecx
    1418:       48 8d 3d 48 15 00 00    lea    0x1548(%rip),%rdi        # 2967 &amp;lt;rand@plt+0x1577&amp;gt;
    141f:       ff 15 b3 fb 01 00       call   *0x1fbb3(%rip)        # 20fd8 &amp;lt;rand@plt+0x1fbe8&amp;gt;
    1425:       f4                      hlt
    1426:       66 2e 0f 1f 84 00 00    cs nopw 0x0(%rax,%rax,1)
    142d:       00 00 00 
    1430:       48 8d 3d d9 fb 01 00    lea    0x1fbd9(%rip),%rdi        # 21010 &amp;lt;rand@plt+0x1fc20&amp;gt;
    1437:       48 8d 05 d2 fb 01 00    lea    0x1fbd2(%rip),%rax        # 21010 &amp;lt;rand@plt+0x1fc20&amp;gt;
    143e:       48 39 f8                cmp    %rdi,%rax
    1441:       74 15                   je     1458 &amp;lt;rand@plt+0x68&amp;gt;
    1443:       48 8b 05 96 fb 01 00    mov    0x1fb96(%rip),%rax        # 20fe0 &amp;lt;rand@plt+0x1fbf0&amp;gt;
    144a:       48 85 c0                test   %rax,%rax
    144d:       74 09                   je     1458 &amp;lt;rand@plt+0x68&amp;gt;
    144f:       ff e0                   jmp    *%rax
    1451:       0f 1f 80 00 00 00 00    nopl   0x0(%rax)

(...)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;program-headers&quot;&gt;Program Headers&lt;/h4&gt;
&lt;p&gt;The program header table provides a &lt;em&gt;segment view&lt;/em&gt; of the binary, as opposed to the &lt;em&gt;section view&lt;/em&gt; provided by the section header table. It's defined as follow:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g8ju6auuns4gk64up863.png&quot; alt=&quot;Image description&quot;&gt;&lt;br&gt;
&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4azsq1vx1s8vi1bos53x.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;In here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;p_type: The type of segments. Common types: LOAD means the segment are intended to be loaded into memory, INTERP means the segment contains &lt;em&gt;.interp&lt;/em&gt; section providing name of the interpreter that is to be used to load the executable, DYNAMIC means the segment contains the &lt;em&gt;.dynamic&lt;/em&gt; section, which tells the interpreter how to parse and prepare the binary for execution, PHDR means the segment encompasses the program header table.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;p_flags: The runtime access permissions for the segments. Common flags: X means executable, R means readable, W means writable or so on.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;p_offset, p_vaddr, p_paddr, p_filesz, p_memsz: These equals to sh_offset, sh_addr, sh_size fields in the section headers.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;p_align: It's equal to sh_addralign field.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can see the program headers with this command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ readelf --segments --wide ./prog

Elf file type is DYN (Position-Independent Executable file)
Entry point 0x1400
There are 13 program headers, starting at offset 64

Program Headers:
  Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
  PHDR           0x000040 0x0000000000000040 0x0000000000000040 0x0002d8 0x0002d8 R   0x8
  INTERP         0x000318 0x0000000000000318 0x0000000000000318 0x00001c 0x00001c R   0x1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x000000 0x0000000000000000 0x0000000000000000 0x000d30 0x000d30 R   0x1000
  LOAD           0x001000 0x0000000000001000 0x0000000000001000 0x019061 0x019061 R E 0x1000
  LOAD           0x01b000 0x000000000001b000 0x000000000001b000 0x004dfc 0x004dfc R   0x1000
  LOAD           0x020cc0 0x0000000000020cc0 0x0000000000020cc0 0x000350 0x000370 RW  0x1000
  DYNAMIC        0x020cd0 0x0000000000020cd0 0x0000000000020cd0 0x000200 0x000200 RW  0x8
  NOTE           0x000338 0x0000000000000338 0x0000000000000338 0x000030 0x000030 R   0x8
  NOTE           0x000368 0x0000000000000368 0x0000000000000368 0x000044 0x000044 R   0x4
  GNU_PROPERTY   0x000338 0x0000000000000338 0x0000000000000338 0x000030 0x000030 R   0x8
  GNU_EH_FRAME   0x01dee8 0x000000000001dee8 0x000000000001dee8 0x0005fc 0x0005fc R   0x4
  GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW  0x10
  GNU_RELRO      0x020cc0 0x0000000000020cc0 0x0000000000020cc0 0x000340 0x000340 R   0x1

 Section to Segment mapping:
  Segment Sections...
   00     
   01     .interp 
   02     .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt 
   03     .init .plt .plt.got .plt.sec .text .fini 
   04     .rodata .eh_frame_hdr .eh_frame 
   05     .init_array .fini_array .dynamic .got .data .bss 
   06     .dynamic 
   07     .note.gnu.property 
   08     .note.gnu.build-id .note.ABI-tag 
   09     .note.gnu.property 
   10     .eh_frame_hdr 
   11     
   12     .init_array .fini_array .dynamic .got 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Until here, I gave you the overall, a bit detailed, presentation about the ELF executable. For next articles, I will dive into the binary analysis techniques in deeply for Linux.&lt;/p&gt;
&lt;p&gt;Resource:&lt;/p&gt;
&lt;p&gt;Andriesse D., Practical Binary Analysis, no starch press, San Francisco.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/14149/elf-executable-analysis-in-detail</guid>
<pubDate>Tue, 07 Apr 2026 11:58:08 +0000</pubDate>
</item>
<item>
<title>Simple Code Injection into ELF Executable</title>
<link>https://coderlegion.com/14091/simple-code-injection-into-elf-executable</link>
<description>&lt;p&gt;In this tutorial, I will explain an code injection technique into ELF executable.&lt;/p&gt;
&lt;p&gt;Firstly, you need to understand the ELF executable, its contents, sections, symbol table or so on. I've written a dedicated article previously. So if you don't have any information about ELF, please read it firstly and then come here again.&lt;/p&gt;
&lt;p&gt;Let's say we have the following source file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/**
  * Simple Code Injection
  */

 #include &amp;lt;stdio.h&amp;gt;
 #include &amp;lt;stdlib.h&amp;gt;
 #include &amp;lt;unistd.h&amp;gt;

int main(int argc, char *argv[])
{
int i, num;

        num = 10;

        for (i = 0; i &amp;lt; num; i++)
        {
              printf(&quot;#%d\n&quot;, i);
        }
        return EXIT_SUCCESS;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Compile this source file and then run it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ gcc main.c -o main ; ./main
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After that, you will see:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-console&quot;&gt; #0
 #1
 #2
 #3
 #4
 #5
 #6
 #7
 #8
 #9
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In here, I will change the &lt;em&gt;num = 10&lt;/em&gt; to &lt;em&gt;num = 5&lt;/em&gt; so that the output will be #0 to #4. &lt;/p&gt;
&lt;p&gt;To accomplish this, I need to find the corresponding machine instruction/s. According to your background, you need to know that &lt;em&gt;num&lt;/em&gt; variable will be in the &lt;em&gt;.text&lt;/em&gt; section of ELF executable. Because it is the local variable. To display the .text section of the program, &lt;em&gt;objdump&lt;/em&gt; is the primarily tool:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ objdump -j .text -d main
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output will be as following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-console&quot;&gt;(...)

0000000000001149 &amp;lt;main&amp;gt;:
    1149:f3 0f 1e fa          endbr64
    114d:55                   push   %rbp
    114e:48 89 e5             mov    %rsp,%rbp
    1151:48 83 ec 20          sub    $0x20,%rsp
    1155:89 7d ec             mov    %edi,-0x14(%rbp)
    1158:48 89 75 e0          mov    %rsi,-0x20(%rbp)
    115c:c7 45 fc 0a 00 00 00 movl   $0xa,-0x4(%rbp)
    1163:c7 45 f8 00 00 00 00 movl   $0x0,-0x8(%rbp)
    116a:eb 1d                jmp    1189 &amp;lt;main+0x40&amp;gt;
    116c:8b 45 f8             mov    -0x8(%rbp),%eax
    116f:89 c6                mov    %eax,%esi
    1171:48 8d 05 8c 0e 00 00 lea    0xe8c(%rip),%rax        # 2004 &amp;lt;_IO_stdin_used+0x4&amp;gt;
    1178:48 89 c7             mov    %rax,%rdi
    117b:b8 00 00 00 00       mov    $0x0,%eax
    1180:e8 cb fe ff ff       call   1050 &amp;lt;printf@plt&amp;gt;
    1185:83 45 f8 01          addl   $0x1,-0x8(%rbp)
    1189:8b 45 f8             mov    -0x8(%rbp),%eax
    118c:3b 45 fc             cmp    -0x4(%rbp),%eax
    118f:7c db                jl     116c &amp;lt;main+0x23&amp;gt;
    1191:b8 00 00 00 00       mov    $0x0,%eax
    1196:c9                   leave
    1197:c3                   ret
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which machine instruction/s corresponds to &lt;em&gt;num = 10&lt;/em&gt;? You need to know reading the assembly instructions in here. The answer is the &lt;strong&gt;movl   $0xa,-0x4(%rbp)&lt;/strong&gt; assembly instruction. Because the stack pointer of the program was enlarged by 4 bytes (&lt;em&gt;int&lt;/em&gt; holds 4 bytes of memory) to lower address and put the immediate 0xA value. So that I need to change the 0xA value to 0x5. &lt;/p&gt;
&lt;p&gt;After the found required assembly instruction, I need to determine the memory address of it. If you look at center column, the machine instruction is &lt;strong&gt;c7 45 fc 0a 00 00 00&lt;/strong&gt;. In here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;c7: the opcode of &lt;em&gt;movl&lt;/em&gt; register&lt;/li&gt;
&lt;li&gt;45 fc: the displacement of -0x4(%rbp)&lt;/li&gt;
&lt;li&gt;0a 00 00 00: the immediate value, 0xA (little-endian)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;c7 byte resides at 0x115c address so that 0a byte at 0x115f.&lt;/p&gt;
&lt;p&gt;To change that byte, I'm gonna use &lt;em&gt;hexedit&lt;/em&gt; program:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ hexedit ./main
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You will see:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6xykdj3162c4xy1fq0pv.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;After that, you move to 0x115F address and change the 0x0A value to 0x05. Save the changes and then run the program again. You will see:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-console&quot;&gt; #0
 #1
 #2
 #3
 #4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's it. You injected the new code in the ELF executable . &lt;/p&gt;
&lt;p&gt;This technique is simple and powerful. But there are some limitations. The most important one is that you should not shift the byte addresses, just replace it. If you wanna change the entire ELF executable addresses, you need the other techniques. That's the next article's topic.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/14091/simple-code-injection-into-elf-executable</guid>
<pubDate>Mon, 06 Apr 2026 20:12:17 +0000</pubDate>
</item>
<item>
<title>Interrupt Management in ARM Cortex-M</title>
<link>https://coderlegion.com/14090/interrupt-management-in-arm-cortex-m</link>
<description>&lt;p&gt;Interrupts is the must-known concept in embedded programming. So I wanna explain the interrupt management in ARM Cortex-M in this tutorial.&lt;/p&gt;
&lt;p&gt;ARM architecture distinguishes between the two types: &lt;em&gt;interrupt&lt;/em&gt; and &lt;em&gt;system exception&lt;/em&gt;. Interrupts are primarily sourced by external hardware like UART that warns about arrival of data. System exceptions are more software-related and caused by ARM Cortex internal.&lt;/p&gt;
&lt;p&gt;Interrupts and system exceptions are exchangeably used. But even if both of these do same thing (especially in programming), the differences are important for us, engineers!&lt;/p&gt;
&lt;p&gt;I'll use &quot;interrupt&quot; word for both terms further, if otherwise declared.&lt;/p&gt;
&lt;p&gt;Before diving into interrupts, you need to understand the general execution workflow of ARM Cortex. There are two types of &lt;em&gt;execution&lt;/em&gt; modes: &lt;em&gt;thread&lt;/em&gt; and &lt;em&gt;interrupt&lt;/em&gt;. After resetting the microcontroller, execution flow starts in thread mode so that if you want to use interrupts, you need to enter the interrupt mode. But how?&lt;/p&gt;
&lt;p&gt;When you flash your firmware into microcontroller (base address of 0x08000000, in case), ARM Cortex starts to execute the firmware's machine instructions in thread mode. If you want to use any interrupt, you have to explicitly say it to ARM Cortex by using &lt;em&gt;Interrupt Service Routines (ISRs)&lt;/em&gt;. ISRs are actually standard C functions, but have the special signatures. I'm gonna explain it later.&lt;/p&gt;
&lt;p&gt;The most important question about interrupts is that why do we need interrupts?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The answer: Interrupts are the source of multiprogramming!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Interrupts give us &lt;em&gt;second&lt;/em&gt; execution flow besides the main flow (executed in thread mode by default). It also gives the &lt;em&gt;context switch&lt;/em&gt; mechanism for RTOS. I will explain it lastly. &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5dvmqafsj7ilkcameuq1.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;ARM Cortex has a special hardware called &lt;em&gt;Nested Vectored Interrupt Controller (NVIC)&lt;/em&gt;. It handles and manages the all interrupt usage. Below is the its simple schematic:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ifm0nlj1u3zt5ujigevn.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;Another important dedicated hardware is the &lt;em&gt;EXTI Controller&lt;/em&gt;. The peripheral interrupts in the microcontroller directly are connected to NVIC. But If you want to use interrupts over microcontroller pins (like detecting rising or falling edge of a button), you have to use EXTI lines. Microcontroller pins are grouped and tied to specific EXTI lines. Please look at the reference manual to find it.&lt;/p&gt;
&lt;p&gt;We have many different types of interrupts. For now, let's look at these:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oxuqk3xcz9kjg3zxrjgh.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;ARM Cortex has defined 15 fixed system exceptions (coming from internal of Cortex). But microcontroller vendors specify also variable-length interrupts (coming from external of Cortex). For example, STM32 series microcontrollers allow up to 256 interrupts.&lt;/p&gt;
&lt;p&gt;But where are located these interrupts? The answer is the &lt;em&gt;at vector table&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The vector table is an array of word data inside the system&lt;br&gt;
memory, each representing the starting address of one exception type [1].&lt;/p&gt;
&lt;p&gt;If you've flashed any firmware into microcontroller over OpenOCD + GDB, you've seen probably the this vector table. If you've not, let's see it exactly. &lt;/p&gt;
&lt;p&gt;I have STM32F446RE microcontroller and ST-LINK connection. I've compiled a firmware (&lt;em&gt;firmware.elf&lt;/em&gt;) to flash it. Open a terminal and type:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ openocd -f interface/stlink.cfg -f target/stm32f4x.cfg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And open the second terminal and type:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ arm-none-eabi-gdb firmware.elf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lastly, type these into GDB session:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(gdb) target remote localhost:3333
(gdb) monitor reset halt
(gdb) load
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After flashed the firmware into microcontroller, you will see:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Loading section .isr_vector, size 0x1c4 lma 0x8000000
Loading section .text, size 0x1080 lma 0x80001d0
Loading section .rodata, size 0x10 lma 0x8001250
Loading section .init_array, size 0x4 lma 0x8001260
Loading section .fini_array, size 0x4 lma 0x8001264
Loading section .data, size 0x10 lma 0x8001268
Start address 0x80011d0, load size 4716
Transfer rate: 9 KB/sec, 786 bytes/write.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yeah! You see the interrupt vector table (_.isr_vector_) section was flashed at the base address of flash memory and other sections after the vector table. If you use any type of interrupt, the ARM Cortex gets the corresponding ISR handler from there so that ARM Cortex exactly knows the ISR handler addresses. You just need to select the required ISR handler function!&lt;/p&gt;
&lt;p&gt;As I said before, ISR handlers are special type of C functions. But why? Because the handlers have predefined function prototypes. You have to use these function prototypes. Otherwise, ARM Cortex cannot find it and treated as normal C functions, not the ISR handlers. &lt;/p&gt;
&lt;p&gt;These predefined handler prototypes are stored in the &lt;em&gt;startup code&lt;/em&gt;. For STM32F446RE, it is _startup_stm32f446xx.S_ file. Below is a part of this startup code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(...)

  .word  Reset_Handler
  .word  NMI_Handler
  .word  HardFault_Handler
  .word  MemManage_Handler
  .word  BusFault_Handler

(...)

  .word     CAN1_TX_IRQHandler                /* CAN1 TX                      */                         
  .word     CAN1_RX0_IRQHandler               /* CAN1 RX0                     */                          
  .word     CAN1_RX1_IRQHandler               /* CAN1 RX1                     */                          
  .word     CAN1_SCE_IRQHandler               /* CAN1 SCE                     */                          
  .word     EXTI9_5_IRQHandler                /* External Line[9:5]s          */                          
  .word     TIM1_BRK_TIM9_IRQHandler          /* TIM1 Break and TIM9          */         
  .word     TIM1_UP_TIM10_IRQHandler          /* TIM1 Update and TIM10        */         
  .word     TIM1_TRG_COM_TIM11_IRQHandler     /* TIM1 Trigger and Commutation and TIM11 */
  .word     TIM1_CC_IRQHandler                /* TIM1 Capture Compare         */                          
  .word     TIM2_IRQHandler                   /* TIM2                         */                   
  .word     TIM3_IRQHandler                   /* TIM3                         */                   
  .word     TIM4_IRQHandler                   /* TIM4                         */                   
  .word     I2C1_EV_IRQHandler                /* I2C1 Event                   */                          
  .word     I2C1_ER_IRQHandler                /* I2C1 Error                   */                          
  .word     I2C2_EV_IRQHandler                /* I2C2 Event                   */                          
  .word     I2C2_ER_IRQHandler                /* I2C2 Error                   */          

(...)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At the beginning, I've stated that interrupts are the source of multiprogramming. This is core logic behind the RTOS!&lt;/p&gt;
&lt;p&gt;RTOSes use the interrupts to create many &lt;em&gt;tasks&lt;/em&gt;. In classic firmware written with RTOS, there are many tasks that the RTOS kernel have to handle and manage. You can think that task in the firmware corresponds to process in the computers. &lt;/p&gt;
&lt;p&gt;The RTOS kernel needs to execute these tasks in order and with same time-slice so that the kernel cuts the one task execution and passes to the next task execution using &lt;em&gt;context switches&lt;/em&gt;. This is done by using interrupts. For example, FreeRTOS kernel uses &lt;em&gt;SVC&lt;/em&gt; and &lt;em&gt;PendSV&lt;/em&gt; interrupts specificly to make the contex switch. Apart from that, the RTOS kernel uses also the &lt;em&gt;SysTick&lt;/em&gt; interrupt to give the equal execution time to each task. &lt;/p&gt;
&lt;p&gt;Got it. I've explain the interrupt management in ARM Cortex-M in general. I put the some references below. You can look at the interrupts and similar topics in there.&lt;/p&gt;
&lt;p&gt;References:&lt;/p&gt;
&lt;p&gt;[1]. Yiu J., The Definitive Guide to The ARM Cortex-M3, Second Edition, Newnes, page 36.&lt;br&gt;
Noviello C., Mastering STM32, 0.26 Release, Leanpub&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/14090/interrupt-management-in-arm-cortex-m</guid>
<pubDate>Mon, 06 Apr 2026 15:15:30 +0000</pubDate>
</item>
<item>
<title>How to build a firmware using Makefile?</title>
<link>https://coderlegion.com/14088/how-to-build-a-firmware-using-makefile</link>
<description>&lt;p&gt;In embedded programming, we trust IDEs like STM32CubeIDE, Keil or so on. These IDEs give us just some buttons and UIs to build, flash, and debug the source code that we've written for a MCU. But, I think that every engineer that works on this field have to know that how these steps are made by hand.&lt;/p&gt;
&lt;p&gt;In this tutorial, I will show building a firmware written for STM32F446RE MCU with just using a single Makefile.&lt;/p&gt;
&lt;p&gt;Below is the project directory:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ tree
.
├── bin/
├── drivers/
│   ├── CMSIS/
│   ├── STM32F446RETX_FLASH.ld
│   └── STM32F4xx_HAL_Driver/
├── lib/
├── Makefile
└── src/
    ├── it.c
    ├── main.c
    ├── main.h
    └── peripheral.c
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Firstly, which libraries do we need to have?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;HAL library&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CMSIS library&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Linker script&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Startup/system code&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;arm-none-eabi-* tools&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I've put the all required libraries/scripts into &lt;strong&gt;/drivers&lt;/strong&gt; directory. Please download these using:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ git clone --recurse-submodules https://github.com/STMicroelectronics/STM32CubeF4.git
$ sudo apt install gcc-arm-none-eabi
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In here, HAL library is used to program the peripherals and CMSIS for ARM-Cortex itself. These are the most important libraries that we have to get. &lt;/p&gt;
&lt;p&gt;Apart from that, we also need some special files. First is the &lt;strong&gt;linker script&lt;/strong&gt;. It includes the memory layout of microcontroller. Sometimes, it comes from above command. But if you cannot find it there, please install it externally. Another file is the &lt;strong&gt;startup code&lt;/strong&gt;. It's written in Assembly language. As its name suggests, it's the first code that runs in microcontroller. Basically, it includes the &lt;strong&gt;Reset_Handler&lt;/strong&gt; and a branch that jumps into &lt;strong&gt;main()&lt;/strong&gt; function in your source code. Also &lt;strong&gt;system code&lt;/strong&gt; includes the some generic and general definitions.&lt;/p&gt;
&lt;p&gt;After downloaded and explained the required ones, Let's write the &lt;strong&gt;Makefile&lt;/strong&gt; step by step:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Toolchain definitions&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CC        := arm-none-eabi-gcc
OBJCOPY:= arm-none-eabi-objcopy
SIZE:= arm-none-eabi-size
RM        := rm -f
FILE:= file
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2. MCU-specific definitions&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DEVICE_FAMILY     := STM32F4xx
DEVICE_MODEL     := STM32F446xx
DEVICE_VARIANT     := STM32F446RETx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;3. Compiler and linker flags&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CORTEX_FLAGS     := -mthumb -mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=hard
COMMON_FLAGS     := -g3 -Os -ffunction-sections -fdata-sections -Wall
AS_FLAGS             := -x assembler-with-cpp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In here, we've defined some flags:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;-mthumb&lt;/strong&gt; means that the firmware uses thumb (16-bit) instruction set.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;-mcpu=cortex-m4&lt;/strong&gt; means the ARM-Cortex series itself.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;-mfpu=fpv4-sp-d16&lt;/strong&gt; and &lt;strong&gt;-mfloat-abi=hard&lt;/strong&gt; means that floating-point operations can be done in the firmware.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. Include paths&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MAIN_INC:= ./src
CMSIS_INC:= ./drivers/CMSIS/Include
CMSIS_DEV_INC:= ./drivers/CMSIS/Device/ST/STM32F4xx/Include
HAL_INC:= ./drivers/STM32F4xx_HAL_Driver/Inc
CMSIS_DSP_INC:= ./drivers/CMSIS/DSP/Include
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In here, we specified the header files that are used in the source code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. Source and special files&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MAIN_SRC:= $(wildcard ./src/*.c)
HAL_SRC:= $(wildcard ./drivers/STM32F4xx_HAL_Driver/Src/*.c)
SYSTEM_SRC:= ./drivers/CMSIS/Device/ST/STM32F4xx/Source/Templates/system_stm32f4xx.c
STARTUP_CODE:= ./drivers/CMSIS/Device/ST/STM32F4xx/Source/Templates/gcc/startup_stm32f446xx.S
LINKER_SCRIPT:= ./drivers/STM32F446RETX_FLASH.ld
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;6. Sorthands and build definitions&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DEFINES:= -D$(DEVICE_FAMILY) -D$(DEVICE_MODEL) -D$(DEVICE_VARIANT) \
-DUSE_HAL_DRIVER

SOURCES:= $(MAIN_SRC) $(HAL_SRC) $(SYSTEM_SRC)
OBJECTS:= $(notdir $(patsubst %.c,%.o,$(SOURCES))) startup_stm32f446xx.o
INCLUDES:= -I$(MAIN_INC) -I$(CMSIS_DEV_INC) -I$(CMSIS_INC) -I$(HAL_INC) \
-I$(CMSIS_DSP_INC)

CFLAGS:= $(CORTEX_FLAGS) $(COMMON_FLAGS) $(DEFINES) $(INCLUDES)
AFLAGS:= $(CORTEX_FLAGS) $(AS_FLAGS) $(DEFINES) $(INCLUDES)
LDFLAGS:= $(CORTEX_FLAGS) -T $(LINKER_SCRIPT) \
-Wl,--gc-sections,--relax --specs=nano.specs --specs=nosys.specs \
   -Wl,--start-group -lc -lm -lnosys -Wl,--end-group
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In here, I mostly did the string operations and manipulations. But some points are interesting and need some explanations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Firstly, I've grouped the sources and libraries and converted the source file suffixes into object file formats.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Also, I've created three total flags that will be used for compiling, assembling, and linking phases.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In linker flags, I put the linker script. As you noticed, this step will be done after the compiling the source files. I also linked the some libraries. &lt;strong&gt;-lc&lt;/strong&gt; is the standard C library. In firmware, we use often string manipulation functions (&lt;strong&gt;strlen()&lt;/strong&gt;, &lt;strong&gt;strcat()&lt;/strong&gt;, &lt;strong&gt;strcpy()&lt;/strong&gt; or so on), &lt;strong&gt;snprint()&lt;/strong&gt;, &lt;strong&gt;memset()&lt;/strong&gt;, or similar ones. &lt;strong&gt;-lm&lt;/strong&gt; is the math library as you know. &lt;strong&gt;-lnosys&lt;/strong&gt; means that I'm just implementing the bare-metal firmware, there is no such as kernel or fully OS.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;7. Output files&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FIRMWARE_ELF:= firmware.elf
FIRMWARE_BIN:= firmware.bin
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Generally, we use the ELF file when flashing and debugging. ELF executable includes the both machine code, metadata, debug information, symbol table or similar stuffs. In contrast, BIN executable just includes the machine code. If you look at the size of both, you will use the ELF executable is much bigger! &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;8. The recipe&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.PHONY: all

all:
@echo &quot;-------------------------------------&quot;
@echo &quot;----- Building the source files -----&quot;
@echo &quot;-------------------------------------&quot;
@$(CC) $(AFLAGS) -c $(STARTUP_CODE)
@$(CC) $(CFLAGS) -c $(SOURCES)

@echo &quot;\n------------------------------------&quot;
@echo &quot;----- Linking the object files -----&quot;
@echo &quot;------------------------------------&quot;
@$(CC) $(LDFLAGS) $(OBJECTS) -o $(FIRMWARE_ELF)
@$(OBJCOPY) -O binary $(FIRMWARE_ELF) $(FIRMWARE_BIN)
@$(RM) $(OBJECTS)

@echo &quot;\n-------------------------------------&quot;
@echo &quot;----- The firmware memory usage -----&quot;
@echo &quot;-------------------------------------&quot;
@$(SIZE) $(FIRMWARE_ELF)

@echo &quot;\n-------------------------------------&quot;
@echo &quot;----- The firmware binary format ----&quot;
@echo &quot;-------------------------------------&quot;
@$(FILE) $(FIRMWARE_ELF)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lastly, run the Makefile:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ make
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's it . You've built the both &lt;strong&gt;firmware.elf&lt;/strong&gt; and &lt;strong&gt;firmware.bin&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;You are ready to flash the built firmware into microcontroller with this or similar command (over ST-Link connection):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ openocd -f interface/stlink.cfg -f target/stm32f4x.cfg -c &quot;program firmware.elf verify reset exit&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I've also published a repository for that. You can look at it from here: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/CanGulmez/Embedded-Firmware-with-Makefile&quot;&gt;https://github.com/CanGulmez/Embedded-Firmware-with-Makefile&lt;/a&gt;&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/14088/how-to-build-a-firmware-using-makefile</guid>
<pubDate>Mon, 06 Apr 2026 15:13:09 +0000</pubDate>
</item>
<item>
<title>Flashing and Debugging A Firmware in Detail</title>
<link>https://coderlegion.com/14087/flashing-and-debugging-a-firmware-in-detail</link>
<description>&lt;p&gt;In this tutorial, I will inspect, flash and debug a firmware written for STM32F446RE microcontroller. To accomplish this, we need some dedicated tools:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;openocd&lt;/li&gt;
&lt;li&gt;arm-none-eabi-*&lt;/li&gt;
&lt;li&gt;gdb-multiarch&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;openocd is the primarily tool that we have to get. It's used as bridge between your host machine and debugger hardware. (I'm using use ST-Link hardware in this time). I will use it to connect to the debugger hardware. arm-none-eabi-* tools are the ARM toolchain   used in development of the firmware. gdb-multiarch is the debugger too.&lt;/p&gt;
&lt;p&gt;You can get these tools:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ sudo apt install openocd gcc-arm-none-eabi gdb-multiarch
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Note: I'm on Ubuntu 24.04, gcc-arm-none-eabi tools don't include the gdb debugger like arm-none-eabi-gdb. So that gdb-multiarch is being installed externally. On some distribution, the gdb debugger comes with gcc-arm-none-eabi.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Suppose that I have a firmware called &quot;firmware.elf&quot; and wanna inspect it as first step. &lt;/p&gt;
&lt;p&gt;The first thing that you should do probably is to determine the firmware itself:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-console&quot;&gt;$ file firmware.elf
firmware.elf: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, with debug_info, not stripped
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;em&gt;file&lt;/em&gt; command simply outputs the firmware properties like the endiannes, architecture, or so on. You can see easily that this firmware was written for 32-bit ARM Cortex.&lt;/p&gt;
&lt;p&gt;We can see also the section sizes (in bytes) of the firmware with &lt;em&gt;arm-none-eabi-size&lt;/em&gt; command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-console&quot;&gt;$ arm-none-eabi-size firmware.elf
   text   data    bss    dec    hexfilename
   5252     20   1732   7004   1b5cfirmware.elf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you are not familiar with sections of the executables, I've wrote many tutorials that explains the ELF executable. You can look at there.&lt;/p&gt;
&lt;p&gt;If you exactly wanna see the contents of the firmware, &lt;em&gt;arm-none-eabi-objdump&lt;/em&gt; is the primarily tool for that:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-console&quot;&gt;$ arm-none-eabi-objdump -j .text -d firmware.elf

firmware.elf:     file format elf32-littlearm


Disassembly of section .text:

(...)

0800079c &amp;lt;main&amp;gt;:
 800079c:b500      push{lr}
 800079e:b091      subsp, #68@ 0x44
 80007a0:f000 f8ac bl80008fc &amp;lt;HAL_Init&amp;gt;
 80007a4:f000 f82c bl8000800 &amp;lt;configOscClk&amp;gt;
 80007a8:f000 f844 bl8000834 &amp;lt;configDebugPort&amp;gt;
 80007ac:4911      ldrr1, [pc, #68]@ (80007f4 &amp;lt;main+0x58&amp;gt;)
 80007ae:4d12      ldrr5, [pc, #72]@ (80007f8 &amp;lt;main+0x5c&amp;gt;)
 80007b0:4c12      ldrr4, [pc, #72]@ (80007fc &amp;lt;main+0x60&amp;gt;)
 80007b2:4668      movr0, sp
 80007b4:f7ff ff08 bl80005c8 &amp;lt;strcpy&amp;gt;
 80007b8:4668      movr0, sp
 80007ba:f7ff ff81 bl80006c0 &amp;lt;strlen&amp;gt;
 80007be:f04f 33ff mov.wr3, #4294967295@ 0xffffffff
 80007c2:b282      uxthr2, r0
 80007c4:4669      movr1, sp
 80007c6:480d      ldrr0, [pc, #52]@ (80007fc &amp;lt;main+0x60&amp;gt;)
 80007c8:f000 fdc2 bl8001350 &amp;lt;HAL_UART_Transmit&amp;gt;
 80007cc:4629      movr1, r5
 80007ce:4668      movr0, sp
 80007d0:f7ff fefa bl80005c8 &amp;lt;strcpy&amp;gt;
 80007d4:4668      movr0, sp
 80007d6:f7ff ff73 bl80006c0 &amp;lt;strlen&amp;gt;
 80007da:f04f 33ff mov.wr3, #4294967295@ 0xffffffff
 80007de:b282      uxthr2, r0
 80007e0:4669      movr1, sp
 80007e2:4620      movr0, r4
 80007e4:f000 fdb4 bl8001350 &amp;lt;HAL_UART_Transmit&amp;gt;
 80007e8:f44f 707a mov.wr0, #1000@ 0x3e8
 80007ec:f000 f8b2 bl8000954 &amp;lt;HAL_Delay&amp;gt;
 80007f0:e7ec      b.n80007cc &amp;lt;main+0x30&amp;gt;
 80007f2:bf00      nop
 80007f4:0800146c .word0x0800146c
 80007f8:08001489 .word0x08001489
 80007fc:20000084 .word0x20000084

(...)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For example, you see the &lt;em&gt;main()&lt;/em&gt; function in &lt;em&gt;.text&lt;/em&gt; section of the firmware. At left, the address of the machine code were being displayed. At center, you see the machine code corresponding to assembly instructions that is at right side.&lt;/p&gt;
&lt;p&gt;Inspecting the contents of ELF executable is huge topic. It has many variants and view-of-points in there.&lt;/p&gt;
&lt;p&gt;&quot;Flashing&quot; term means writing the dedicated sections of the firmware into the flash memory area of the microcontroller. It's done with many different ways. But I will show you openocd tool.&lt;/p&gt;
&lt;p&gt;In here, I have to define that how exactly does openocd work. It actually creates a server into the host machine with given configuration files. After starting the server session, you need to connect to that server over telnet or gdb. &lt;/p&gt;
&lt;p&gt;Let's do it. Firstly, connect the microcontroller to the host machine. Then open a terminal session and type this command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ openocd -f interface/stlink.cfg -f target/stm32f4x.cfg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In here, I gave the two configuration files. First is the interface. As I said, I use the ST-Link connection. Second is the target file. It defines the target architecture. The openocd comes a bunch of configuration files. So search it and find the appropriate configuration files.&lt;/p&gt;
&lt;p&gt;You will see something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-console&quot;&gt;Open On-Chip Debugger 0.12.0
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
Info : auto-selecting first available session transport &quot;hla_swd&quot;. To override use 'transport select &amp;lt;transport&amp;gt;'.
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : clock speed 2000 kHz
Info : STLINK V2J33M25 (API v2) VID:PID 0483:374B
Info : Target voltage: 3.224656
Info : [stm32f4x.cpu] Cortex-M4 r0p1 processor detected
Info : [stm32f4x.cpu] target has 6 breakpoints, 4 watchpoints
Info : starting gdb server for stm32f4x.cpu on 3333
Info : Listening on port 3333 for gdb connections
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The openocd attached the 4444 port to telnet and 3333 port to gdb. So that I will bind the 3333 port in the gdb session.&lt;/p&gt;
&lt;p&gt;Secondly, open the second terminal and type:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ gdb-multiarch firmware.elf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At the moment, you are in the gdb session. In here, bind to the 3333 port:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-console&quot;&gt;(gdb) target remote localhost:3333
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For now, you are in the control and responsible for the all flashing and debugging operations.&lt;/p&gt;
&lt;p&gt;To flash the firmware, use these commands:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-console&quot;&gt;(gdb) monitor reset halt
(gdb) load
(gdb) monitor reset halt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are tons of commands you can type in the gdb session. We can split these commands in two categories. &lt;em&gt;monitor &amp;lt;command-name&amp;gt;&lt;/em&gt; commands directly are run by the openocd command. Other commands are run by the gdb itself.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;load&lt;/em&gt; is the main command to flash the firmware. Before and after that command, resetting and halting the CPU execution is good practice. Output will be like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;Loading section .isr_vector, size 0x1c4 lma 0x8000000
Loading section .text, size 0x126c lma 0x8000200
Loading section .rodata, size 0x4c lma 0x800146c
Loading section .ARM, size 0x8 lma 0x80014b8
Loading section .init_array, size 0x4 lma 0x80014c0
Loading section .fini_array, size 0x4 lma 0x80014c4
Loading section .data, size 0xc lma 0x80014c8
Start address 0x08001400, load size 5272
Transfer rate: 9 KB/sec, 753 bytes/write.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That means that the firmware successfully was flashed .&lt;/p&gt;
&lt;p&gt;If you just wanna flash the firmware and not debugging, this following command is enough:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ openocd -f interface/stlink.cfg -f target/stm32f4x.cfg -c &quot;program firmware.elf verify reset exit&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of dealing with two terminal sessions, above command is just the one-step operation.&lt;/p&gt;
&lt;p&gt;The main reason opening the second gdb session is to make the debugging. If you did the debugging previously, you will be familiar with these commands:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-console&quot;&gt;(gdb) break main
(gdb) continue
(gdb) next
(gdb) finish
(gdb) print var
(gdb) x/16wx *ptr
(gdb) set var
(gdb) list
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The debugging operations are huge and will not fit into a single tutorial as you guest. So that I don't wanna dive into the these and other operations. But I wanna give you a good reference: &lt;em&gt;The Art of Debugging with GDB, DDD, and Eclipse&lt;/em&gt; written by Norman Matloff and Peter Jay Salzman.&lt;/p&gt;
&lt;p&gt;Until here, I gave a overall explanation the end-to-end pipeline. I'll explain the other stuffs in further. &lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/14087/flashing-and-debugging-a-firmware-in-detail</guid>
<pubDate>Mon, 06 Apr 2026 14:53:30 +0000</pubDate>
</item>
</channel>
</rss>