<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>Leandro Proença</title>
<link>https://leandronsp.com</link>
<description>low-level curiosity, high-level pragmatism</description>
<atom:link href="https://leandronsp.com/feed.xml" rel="self" type="application/rss+xml"/>
<item>
<title>Taming non-determinism: from logic gates to LLMs</title>
<link>https://leandronsp.com/articles/taming-non-determinism-from-logic-gates-to-llms.html</link>
<guid>https://leandronsp.com/articles/taming-non-determinism-from-logic-gates-to-llms.html</guid>
<description><![CDATA[<p>Or: <em>how engineering keeps turning chaos into reliable computation. And why agentic AI still hasn't solved this.</em></p>
<p>There's a pattern that repeats across the entire history of computing: we take something fundamentally <strong>non-deterministic</strong> and engineer enough layers on top of it until it behaves deterministically. Logic gates did it. Artificial neural networks (ANNs) replicated it at some level. LLMs are the next frontier, and the hardest one yet.</p>
<p>In this article, we'll explore how engineering is crucial to facing non-determinism, and analyse the current state of the art for LLMs.</p>
<hr />
<h2>Agenda</h2>
<ul>
<li>
<p><a href="#part-i---the-lie-inside-a-logic-gate">Part I - The lie inside a logic gate</a></p>
<ul>
<li><a href="#noise-margins">Noise margins</a></li>
<li><a href="#the-cpu-clock">The CPU clock</a></li>
</ul>
</li>
<li>
<p><a href="#part-ii---aspirina-teaching-non-determinism-to-behave">Part II - Aspirina: teaching non-determinism to behave</a></p>
<ul>
<li><a href="#where-the-chaos-lives---unleash-the-madness">Where the chaos lives</a></li>
<li><a href="#composition-does-the-rest">Composition does the rest</a></li>
<li><a href="#the-cost-compared">The cost compared</a></li>
</ul>
</li>
<li>
<p><a href="#part-iii---llms-and-the-unresolved-problem">Part III - LLMs and the "Unresolved Problem"</a></p>
<ul>
<li><a href="#tests-as-noise-margins">Tests as noise margins</a></li>
<li><a href="#the-agentic-loop-as-clock">The agentic loop as clock</a></li>
<li><a href="#the-type-system-and-compiler-as-low-level-validators">The type system and compiler</a></li>
<li><a href="#whats-missing-the-equivalent-of-timing-analysis">What's missing: timing analysis</a></li>
<li><a href="#how-to-mitigate-that">How to mitigate that?</a></li>
</ul>
</li>
<li>
<p><a href="#conclusion">Conclusion</a></p>
</li>
</ul>
<hr />
<h2>Part I - The lie inside a logic gate</h2>
<p>A <a href="https://en.wikipedia.org/wiki/Logic_gate">logic gate</a> is taught in school as a simple binary machine. You give it 0s and 1s, you get 0s and 1s back.</p>
<p>AND, OR and XOR are example of logic gates. They are clean, mathematical, deterministic. And that's a useful <em>fiction</em>.</p>
<p>At the physical level, a gate is a transistor (or a few of them) through which electrons flow. Electrons don't care about your <strong>Boolean algebra</strong>. The voltage on a wire is a continuous, analog value that fluctuates due to thermal noise, manufacturing variance, electromagnetic interference, and even cosmic radiation flipping bits in memory.</p>
<blockquote>
<p>Fun fact: in May 2003, in Belgium, an electronic voting machine gave a candidate <a href="https://en.wikipedia.org/wiki/Electronic_voting_in_Belgium">exactly 4096 extra votes</a>. More than she could possibly have received. After investigation, the error was attributed to the <strong>spontaneous creation of a 13th bit in the memory of the computer</strong>. The leading explanation: a cosmic ray flipped a single bit, turning a 0 into a 1 at position 2^12, adding exactly 4096 to the candidate count.</p>
</blockquote>
<p>Ok, but how does the industry get determinism out of this mess?</p>
<h3>Noise margins</h3>
<p>Engineers define voltage thresholds: anything below 0.8V is a <code>0</code>, anything above 2.0V is a <code>1</code>. The zone between those values is declared "forbidden". The circuit is designed to never operate stably there. This isn't physics; it's an engineering contract. (<a href="https://en.wikipedia.org/wiki/Noise_margin">More on noise margins</a>)</p>
<h3>The CPU clock</h3>
<p>A CPU clock isn't just a metronome. It's also a <em>sampling strategy</em>. You let the signal propagate through the gate (which takes time, called <strong>propagation delay</strong>), and only read the value at a specific moment: the <em>clock edge</em>. By then, the signal has had time to cross the threshold and stabilise. Timing is calculated to guarantee this.</p>
<p><strong>But when timing fails, things may get weird</strong>. If a signal is sampled while it's still in the forbidden zone, a flip-flop can enter <a href="https://en.wikipedia.org/wiki/Metastability_(electronics)">metastability</a>, which is an unstable equilibrium where the output isn't quite <code>0</code> or <code>1</code> and can oscillate for an indeterminate time. This may cause real crashes in real systems.</p>
<blockquote>
<p>Think of metastability as what happens when two clocks on a wall are unsynchronized. Each showing a slightly different time. "What time is it right now?", one may ask. The answer depends on which clock you look at, but you can't decide which one is right.</p>
</blockquote>
<p>Inside the CPU, when facing a forbidden zone, the flip-flop faces the same dilemma. It can't decide if it's a <code>0</code> or a <code>1</code>, which can corrupt the system state. Engineers mitigate this with synchronizer chains, but never eliminate it entirely.</p>
<p><em>It's the non-determinism of physics leaking through the engineering abstraction</em>!</p>
<p><strong>The key insight</strong>: the determinism of digital computing is not a property of nature. It's an <em>engineering achievement</em>. It's paid with noise margins, timing analysis and careful design.</p>
<p>Yes, it's all about <strong>engineering</strong>.</p>
<hr />
<h2>Part II - Aspirina: teaching non-determinism to behave</h2>
<p><a href="https://github.com/leandronsp/aspirina">Aspirina</a> is my personal project that builds a complete CPU entirely from artificial neural networks (ANNs) trained to behave as logic gates, written in Rust.</p>
<blockquote>
<p>Actually I created the <a href="https://github.com/leandronsp/morphine">very first version</a> almost 10 years ago in Elixir, when I was learning about ANNs and Elixir. Recently I decided to write a Rust version while I was learning Rust.</p>
</blockquote>
<p>The inversion from Part I brings the beauty: instead of physical electrons that need to be tamed into bits, here we have ANNs - <em>systems whose weights are initialised randomly and whose outputs are continuous floating-point numbers</em> - that need to be tamed into <code>0</code> and <code>1</code> too.</p>
<p>The non-determinism here is introduced deliberately, and then engineered away. Bear with me.</p>
<h3>Where the chaos lives - unleash the madness</h3>
<p>A neural network starts with random weights. Its output for any input is whatever the math happens to produce. Not <code>0</code>, not <code>1</code>, but something like <code>0.6312</code>. In case you missed, I already have an article in my blog about <a href="https://leandronsp.com/articles/ai-ruby-an-introduction-to-neural-networks-23f3">neural networks</a>.</p>
<p><strong>Backpropagation is the engineering response</strong>. It's the algorithm that adjusts the weights iteratively, measuring how wrong the output is (the <em>loss</em>) and leading weights in the direction that reduces the error. Run this for 10,000 epochs - 10,000 passes over the training data - and the network converges to weights that produce <code>0.002</code> for inputs that should be <code>0</code>, and <code>0.997</code> for inputs that should be <code>1</code>.</p>
<p>The <a href="https://en.wikipedia.org/wiki/Sigmoid_function">sigmoid function</a> (a.k.a the <em>logistic curve</em>) does in Aspirina what noise margins do in hardware: it squashes any continuous value into the range <code>0..1</code>. It doesn't give you exact binary outputs, but it pushes values toward the extremes.</p>
<blockquote>
<p>And that's exactly what we need</p>
</blockquote>
<p>Combined with a threshold decision - below <code>0.5</code> we could consider <code>0</code>, above <code>0.5</code> we consider <code>1</code> -, we get in Aspirina the same effect: a circuit that <em>behaves deterministically</em> even though its internals are continuous and learned.</p>
<h3>Composition does the rest</h3>
<p>Once gates are trained, they're composed to:</p>
<pre><code>XOR + AND =&gt; Half Adder =&gt; Full Adder =&gt; 4-bit ALU =&gt; Memory =&gt; Registers =&gt; CPU =&gt; Assembler =&gt; Interpreter
</code></pre>
<p>Each layer treats the layer below as if it were perfectly deterministic, which is <strong>exactly the abstraction hierarchy</strong> of real hardware.</p>
<p>The non-determinism was contained at the lowest level (training), and every layer above it benefits from the illusion of determinism.</p>
<p>Yes, <strong>determinism is an illusion</strong>. Deal with it.</p>
<h3>The cost compared</h3>
<p>Compute time during training. Just as chip fabrication invests energy upfront to create reliable silicon, Aspirina invests 10,000 epochs upfront to create reliable logic. After that, inference is cheap and stable.</p>
<p>Now, <em>enter LLMs</em>.</p>
<hr />
<h2>Part III - LLMs and the "Unresolved Problem"</h2>
<p>A <a href="https://en.wikipedia.org/wiki/Large_language_model">large language model</a> (LLM) is non-deterministic in a deeper and more stubborn way than either of the above.</p>
<p>Its weights are the result of a stochastic training process over vast data. Its output is sampled probabilistically (even with <code>temperature=0</code>), and there's pseudo-randomness baked in. And crucially, unlike a neural network trained on a clean truth table, an LLM's "correct output" for most inputs is <em>fundamentally ambiguous</em>.</p>
<p>Yet we're building agentic systems (Claude Code, Codex, Cursor etc) that use LLMs to write, run and iterate on code autonomously. <strong>How do we tame this?</strong></p>
<p>The pattern repeats, but the engineering is particularly <em>messier</em> on LLM's.</p>
<h3>Tests as noise margins</h3>
<p>A test suite could behave as a binary verdict on the LLM's  output: <em>green or red</em>. It doesn't matter if the model generated three different valid implementations in three runs: if all tests pass, they're equivalent from the system's perspective. The non-determinism of the model is <em>contained</em> below the threshold of "passes tests". This works, but only as well as the tests themselves. And unlike voltage thresholds, test coverage is always incomplete.</p>
<h3>The agentic loop as clock</h3>
<p>An agentic system like Claude Code doesn't generate once and deliver. It reads state (files, compiler output, test results), acts, observes the new state, and repeats. This feedback loop is structurally similar to the <em>fetch-decode-execute</em> cycle of a CPU or the iterative of Aspirina. Each iteration constrains the space of valid next actions. Errors are observable, and the agent can correct.</p>
<h3>The type system and compiler as low-level validators</h3>
<p>In a Rust project like Aspirina, the borrow checker <em>rejects invalid outputs before tests even run</em>. <em>The LLM can generate wrong code and hallucinate, the compiler then refuses it, the agent observes the error message, and iterates.</em></p>
<p><strong>This is a lower-level noise margin</strong>: just a formal filter beneath the test layer and type system.</p>
<blockquote>
<p>And that's why I think that languages with strict compilers like Rust will thrive in the agentic era. Let's see.</p>
</blockquote>
<h3>What's missing: the equivalent of timing analysis</h3>
<p>In hardware, engineers can <em>prove</em> that a circuit will work at a given clock frequency. They run static timing analysis and guarantee that every signal stabilises before every clock edge. There's no equivalent for agentic LLM systems.</p>
<p>We don't know how many iterations an agent will need. We can't bound the convergence time. We can't guarantee termination. An agent can loop indefinitely, or enter what I'd call "agentic metastability": making and undoing the same change repeatedly, unable to reach a stable state.</p>
<p>Anyone who has used Claude Code or Codex for long enough has seen this - the model oscillates between two approaches, each creating a problem that motivates reverting to the other.</p>
<blockquote>
<p>Despite we can retry, add guardrails, skills, and set token budgets, in the end, we can't prove termination the way a hardware engineer proves timing closure on metastability</p>
</blockquote>
<h3>How to mitigate that?</h3>
<p>In hardware, the fix for metastability is <strong>design</strong>. In agentic systems, the analog fix is <strong>clearer context, more granular tests, explicit checkpoints and tighter feedback loops</strong>; which can be described with the following practical list of skills I created and have been using on a daily basis for every project in production:</p>
<ul>
<li>
<p><strong>PO (product owner)</strong>: agent receives a prompt and outputs a well-scoped task based on initial requirements and prior knowledge on the codebase. Clearer context.</p>
</li>
<li>
<p><strong>TDD (test-driven development)</strong>: the developer agent outputs the code, guided by TDD - red, green, refactor. Granular tests as noise margins.</p>
</li>
<li>
<p><strong>Review</strong>: agent and human checks before merging. Explicit checkpoint.</p>
</li>
<li>
<p><strong>Small PRs</strong>: small PR's enable development and testing with reviewable increments. Tighter feedback loops.</p>
</li>
</ul>
<p>Yes, that's <strong>software engineering practices</strong> all the way down (and they are not new). Yet we don't have the formal tools <em>to prove</em> these fixes work. It's totally empirical.</p>
<hr />
<h2>Conclusion</h2>
<p>At least for me, what's interesting is that each level's "solved" status depends on <em>bounded, well-defined tasks</em>. Logic gates are deterministic for Boolean logic. Aspirina's networks converge reliably because truth tables are finite and exact. LLMs work well on narrow, testable tasks with earlier and good feedback.</p>
<p>The frontier is: <strong>what does it take to engineer reliable agentic behaviour on open-ended, ambiguous, long-horizon tasks?</strong> The answer probably looks like what came before: more layers of verification, better sampling strategies (the clock), formal specifications (the truth table), and tools that can analyse convergence before execution. <em>We just don't have those yet</em>.</p>
<p>Non-determinism doesn't go away. It never has. We just keep finding better ways to push it where it can't hurt us. From voltage thresholds to synchronizer chains to temperature tuning. Every generation of computing has faced the same enemy and responded with the same weapon: <strong>engineering</strong>. Besides hype, LLMs are no different. We're just earlier in the cycle.</p>
<hr />
<h2>References</h2>
<p>https://en.wikipedia.org/wiki/Logic_gate
https://en.wikipedia.org/wiki/Electronic_voting_in_Belgium
https://en.wikipedia.org/wiki/Noise_margin
https://en.wikipedia.org/wiki/Metastability_(electronics)
https://en.wikipedia.org/wiki/Sigmoid_function
https://en.wikipedia.org/wiki/Large_language_model
https://en.wikipedia.org/wiki/Backpropagation
https://en.wikipedia.org/wiki/Static_timing_analysis
https://github.com/leandronsp/aspirina
https://github.com/leandronsp/morphine
https://leandronsp.com/articles/ai-ruby-an-introduction-to-neural-networks-23f3</p>
]]></description>
<pubDate>2026-02-19</pubDate>
</item>
<item>
<title>Understanding Recursion Fundamentals</title>
<link>https://leandronsp.com/articles/understanding-recursion-fundamentals.html</link>
<guid>https://leandronsp.com/articles/understanding-recursion-fundamentals.html</guid>
<description><![CDATA[<p>If for you:</p>
<ul>
<li>
<p><strong>Recursion</strong> is an obscure topic or you want to understand it a bit better;</p>
</li>
<li>
<p><strong>Tail call and TCO</strong> are alien communication methods and;</p>
</li>
<li>
<p><strong>Trampoline</strong> is a medicine name</p>
</li>
</ul>
<p><em>Then this article is for you.</em></p>
<p>Here, I'll explain what these terms are in a didactic way and the problems they solve, with examples in <strong>Ruby</strong>. But don't worry because the examples are quite simple to understand, especially since the concepts shown here are <em>language-agnostic</em>.</p>
<p>So, come with me on this <strong>endless</strong> journey.</p>
<blockquote>
<p>✋ To continue, go back to the beginning</p>
</blockquote>
<p><em>Note: This is an English translation of the original article in Portuguese: <a href="https://leandronsp.com/articles/entendendo-fundamentos-de-recursao-2ap4">Entendendo fundamentos de recursão</a></em></p>
<hr />
<h2>Agenda</h2>
<ul>
<li>
<p><a href="#what-is-recursion">What is recursion</a></p>
</li>
<li>
<p><a href="#meet-fibo">Meet Fibo</a></p>
</li>
<li>
<p><a href="#tail-call">Tail call</a></p>
</li>
<li>
<p><a href="#stack-and-stack-overflow">Stack and stack overflow</a></p>
</li>
<li>
<p><a href="#tail-call-optimization">Tail call optimization</a></p>
</li>
<li>
<p><a href="#trampoline">Trampoline</a></p>
</li>
<li>
<p><a href="#conclusion">Conclusion</a></p>
</li>
<li>
<p><a href="#references">References</a></p>
</li>
</ul>
<hr />
<h2>What is recursion</h2>
<p>In computer programs, we're used to <strong>breaking large problems into smaller problems</strong> through the use of functions or methods.</p>
<p><strong>Recursion</strong> is, in an extremely simplified way, a technique in computing where these problems are broken down so that a <em>certain function is executed recursively</em>.</p>
<p>With this, the function "calls itself" to solve some computation and continue its execution.</p>
<hr />
<h2>Meet Fibo</h2>
<p>A very classic example of recursion is discovering, given the <strong>Fibonacci sequence</strong>, or Fibo, which number is found at a certain position.</p>
<pre><code>0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55.........
</code></pre>
<p>With this in place, the <strong>fib</strong> function would return results like:</p>
<pre><code>fib(0) = 0
fib(1) = 1
fib(2) = 1
...
fib(7) = 13
fib(10) = 55
</code></pre>
<p>We then have a possible recursive implementation in Ruby:</p>
<pre><code class="language-ruby">def fib(position)
  return position if position &lt; 2

  fib(position - 1) + fib(position - 2)
end
</code></pre>
<p>This code, however, is not performant. When trying to find the number at position <code>10_000</code> (ten thousand) in the sequence, the program becomes very slow because it makes numerous <strong>redundant recursive calls</strong>.</p>
<pre><code>                 fib(10)
             /                \
     fib(9)                 fib(8)
        /          \          /   \
fib(8)     fib(7)     fib(7)    fib(6)
  /      \       /       \       /   \
fib(7) fib(6) fib(6) fib(5) fib(6) fib(5)
   /    \     /     \     /     \     /    \
fib(6) fib(5) fib(5) fib(4) fib(5) fib(4) fib(5) fib(4)
  /   \   /   \   /   \   /   \   /   \   /   \   /   \
...
</code></pre>
<p>Consequently, the larger the function input, the execution time of this code tends to grow exponentially, which in <strong>Big-O</strong> notation would be <code>O(2^n)</code>.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n9udpxmgl34093mpmqvc.png" alt="big O exponential" /></p>
<p>Is it possible to reduce this complexity?</p>
<p>What if we try to apply a technique where the last function call, instead of being the sum of two recursive calls, becomes just <strong>one recursive call</strong>, without performing additional computations?</p>
<p>This technique exists and is called <strong>tail call</strong>, or <em>tail recursion</em>.</p>
<hr />
<h2>Tail call</h2>
<p><strong>Tail call</strong>, or <strong>TC</strong>, consists of a recursive function where the last recursive call is the function itself without additional computation.</p>
<p>With this in place, we reduce the complexity from exponential to linear, as if it were a <em>simple loop iterating over a list of inputs</em>.</p>
<p>In Big-O notation this becomes <code>O(n)</code>, meaning the complexity grows linearly following the growth of the input.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z8uj2fzw9090971awu89.png" alt="big O linear" /></p>
<p>Example in Ruby:</p>
<pre><code class="language-ruby">def fib(position, _current = 0, _next = 1)
  return _current if position &lt; 1

  fib(position - 1, _next, _current + _next)
end
</code></pre>
<p>Therefore, the number of recursive calls is drastically reduced to something like:</p>
<pre><code>fib(10, 0, 1)
fib(9, 1, 1)
fib(8, 1, 2)
fib(7, 2, 3)
fib(6, 3, 5)
fib(5, 5, 8)
fib(4, 8, 13)
fib(3, 13, 21)
fib(2, 21, 34)
fib(1, 34, 55)
fib(0, 55, 89)
</code></pre>
<p>Notice how the number of recursive calls decreased, meaning the code is following a more <strong>linear</strong> path with this approach.</p>
<p>Thus, when running the <strong>fib with TC</strong> program, the execution time is exponentially less than running without TC, being <em>tens of thousands of times faster</em>.</p>
<blockquote>
<p>✋
Clearly a program that takes exponential time is terribly poor performance-wise, right?</p>
</blockquote>
<pre><code class="language-ruby"># Without TC
fib(30) # 0.75 seconds

# With TC
fib(30) # 0.000075 seconds
</code></pre>
<p>Going back to the example of <code>fib(10000)</code>, when running with TC, we see that execution is much faster, however:</p>
<pre><code>recursion/fib.rb:10:in `fib_tc': stack level too deep (SystemStackError)
        from recursion/fib.rb:10:in `fib_tc'
        from recursion/fib.rb:10:in `fib_tc'
        from recursion/fib.rb:10:in `fib_tc'
        from recursion/fib.rb:10:in `fib_tc'
        from recursion/fib.rb:10:in `fib_tc'
        from recursion/fib.rb:10:in `fib_tc'
        from recursion/fib.rb:10:in `fib_tc'
        from recursion/fib.rb:10:in `fib_tc'
</code></pre>
<p><em>Uh oh</em>, a <strong>stack overflow!</strong></p>
<p>To understand what's happening, let's first understand what the heck is a <strong>stack</strong> and <strong>stack overflow</strong>.</p>
<hr />
<h2>Stack and stack overflow</h2>
<p>When a program is executed, a data structure in the form of a <em>stack</em>, called <strong>Stack</strong> (duh), is allocated in memory and is used to store the data being used in a running function.</p>
<blockquote>
<p>✋
There's also another structure in the program's memory called <strong>Heap</strong>, which is not a stack and has other traits that are beyond the scope of this article. To understand recursion, we focus only on the stack</p>
</blockquote>
<p>When the program enters a function or method, each piece of data is <em>pushed onto the stack</em>. When the function finishes, the <em>removal (pop) of each piece of data</em> is done.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rstqf6yz2yph2d7byeuq.png" alt="stack" /></p>
<p>With each function call, a new <em>stack frame</em> is assigned. Since a recursive call never ends, the runtime doesn't know it needs to "pop" the data and finish the frame, so at every call, a new stack frame is created and <strong>more elements are added</strong> to the stack.</p>
<p>Guess what happens when we add too much data to the stack to the point of <strong>exceeding its limit</strong> in the computer's memory?</p>
<p>Yes, the infamous <strong>Stack overflow</strong> happens 💥🪲, and this explains that error in Ruby when running fib of 10000 with tail call.</p>
<blockquote>
<p>✋
So does that mean calculating fib of 10000 is an impossible problem to solve with recursion?</p>
</blockquote>
<p>Hold on, some languages employ an optimization technique that consists of using the tail call with <em>just one stack frame</em>, hence ensuring that each recursive call is treated as if it were <strong>an iteration in a primitive loop.</strong></p>
<p>With this, the function's arguments and data are manipulated in a single stack frame, exactly as if we had written a primitive loop. And consequently, new tail recursive calls won't cause stack overflow.</p>
<p>We call this technique <strong>Tail call optimization</strong>, or <em>TCO</em>.</p>
<hr />
<h2>Tail call optimization</h2>
<p>Due to its imperative nature, and like several other general-purpose languages, <em>Ruby doesn't have native TCO support</em>.</p>
<p>Usually this optimization is more commonly found in languages with a strong inclination toward the functional paradigm, rather than the imperative one.</p>
<p>But in Ruby it's possible to <em>enable TCO mode</em> with a simple configuration in the Ruby runtime instruction (YARV), and thus we can execute fib of 10000 without pain.</p>
<pre><code class="language-ruby">RubyVM::InstructionSequence.compile_option = {
  tailcall_optimization: true,
  trace_instruction: false
}

def fib(position, _current = 0, _next = 1)
  return _current if position &lt; 1

  fib(position - 1, _next, _current + _next)
end

# TC with TCO
fib(10000) # 0.02 seconds
</code></pre>
<p><strong>Superb</strong>! With TCO enabled, a fib 10000 with tail call is executed in <em>0.02 seconds</em>!</p>
<p><em>It's worth remembering that TCO is a technique used not only in recursion but also in instruction generation optimization in compilers, but this is beyond the scope of this article.</em></p>
<blockquote>
<p>✋
Okay, but what if it's not possible to enable TCO for tail recursion or I'm programming in a language that doesn't have TCO support?</p>
</blockquote>
<p><strong>Trampoline</strong> to the rescue.</p>
<hr />
<h2>Trampoline</h2>
<p>To understand <em>trampoline</em>, let's think about the problem and a possible solution.</p>
<p>If we think smart, we can initially conclude that recursion should be avoided, and this is <em>premise number one</em>.</p>
<pre><code class="language-ruby">def fib(position, _current = 0, _next = 1)
  return _current if position &lt; 1

  ###################################
  #### We must avoid this!!!!!! ####
  ###################################
  fib(position - 1, _next, _current + _next)
end
</code></pre>
<p>Premise two, instead of returning a recursive call directly, what if we return it <strong>encapsulated in an anonymous function structure that stores context</strong> to be executed in another context?</p>
<blockquote>
<p>Yes, like a closure or lambda for the more attentive readers</p>
</blockquote>
<p>In Ruby, we can use the concept of <strong>lambdas</strong>.</p>
<pre><code class="language-ruby">def fib(position, _current = 0, _next = 1)
  return _current if position &lt; 1

  lambda do
    fib(position - 1, _next, _current + _next)
  end
end
</code></pre>
<p>If we call <code>result = fib(0)</code>, because of the first line's short-circuit (<code>position &lt; 1</code>), the method's return is <code>0</code>.</p>
<p>But if we call <code>result = fib(10)</code>, the return won't be a recursive call, but rather the <strong>return will be an anonymous function</strong> (lambda).</p>
<p>By doing this, the method is then finished and the <em>stack is cleared</em>, meaning the <strong>pop of data</strong> is done from within the method.</p>
<p>Since lambdas store context, if we call <code>result.call</code>, the lambda is executed with the previous context, which can return the final number (if it enters the short-circuit) or another lambda with the new context.</p>
<p>And so, <strong>we loop until we have the final value</strong>, while the current return continues to be a lambda. Did you understand what we can do?</p>
<p>Yes, a <em>loop!</em></p>
<pre><code class="language-ruby">result = fib(10000)

while result.is_a?(Proc)
  result = result.call
end

puts result
</code></pre>
<p>Output (a really very large number):</p>
<pre><code>33644764876431783266621612005107543310302148460680063906564769974680081442166662368155595513633734025582065332680836159373734790483865268263040892463056431887354544369559827491606602099884183933864652731300088830269235673613135117579297437854413752130520504347701602264758318906527890855154366159582987279682987510631200575428783453215515103870818298969791613127856265033195487140214287532698187962046936097879900350962302291026368131493195275630227837628441540360584402572114334961180023091208287046088923962328835461505776583271252546093591128203925285393434620904245248929403901706233888991085841065183173360437470737908552631764325733993712871937587746897479926305837065742830161637408969178426378624212835258112820516370298089332099905707920064367426202389783111470054074998459250360633560933883831923386783056136435351892133279732908133732642652633989763922723407882928177953580570993691049175470808931841056146322338217465637321248226383092103297701648054726243842374862411453093812206564914032751086643394517512161526545361333111314042436854805106765843493523836959653428071768775328348234345557366719731392746273629108210679280784718035329131176778924659089938635459327894523777674406192240337638674004021330343297496902028328145933418826817683893072003634795623117103101291953169794607632737589253530772552375943788434504067715555779056450443016640119462580972216729758615026968443146952034614932291105970676243268515992834709891284706740862008587135016260312071903172086094081298321581077282076353186624611278245537208532365305775956430072517744315051539600905168603220349163222640885248852433158051534849622434848299380905070483482449327453732624567755879089187190803662058009594743150052402532709746995318770724376825907419939632265984147498193609285223945039707165443156421328157688908058783183404917434556270520223564846495196112460268313970975069382648706613264507665074611512677522748621598642530711298441182622661057163515069260029861704945425047491378115154139941550671256271197133252763631939606902895650288268608362241082050562430701794976171121233066073310059947366875
</code></pre>
<p>🔑 <strong>Key point</strong>
And with this, friends, we have the <strong>trampoline</strong> technique: a non-recursive primitive <strong>loop</strong> that keeps calling another function <em>written recursively</em> but that returns a lambda with context, <strong>until reaching the final value</strong>.</p>
<p>This code, <strong>without TCO</strong>, for <strong>fib of 10000</strong>, takes 0.04 seconds, a result very close to TCO and without causing stack overflow.</p>
<p><em>Incredible, right?</em> Now there are no excuses for not writing a function recursively in languages that don't have TCO support 😛</p>
<hr />
<h2>Conclusion</h2>
<p>In this article, the intent was to bring some concepts and fundamentals around the <strong>recursion</strong> topic. These concepts overlap with very academic topics that sometimes make it difficult for people who are starting in the field or who don't have a very academic background to understand.</p>
<p>I hope I've clarified the recursion subject in a didactic way. If you can, leave any corrections or relevant information in the comments.</p>
<hr />
<h2>References</h2>
<p>https://en.wikipedia.org/wiki/Fibonacci_sequence
https://en.wikipedia.org/wiki/Recursion
https://www.geeksforgeeks.org/stack-data-structure/
https://en.wikipedia.org/wiki/Tail_call
https://en.wikipedia.org/wiki/Trampoline_(computing)
https://nithinbekal.com/posts/ruby-tco/
https://www.bigocheatsheet.com/
https://ruby-doc.org/core-3.1.0/RubyVM/InstructionSequence.html#method-c-compile_option</p>
]]></description>
<pubDate>2025-11-14</pubDate>
</item>
<item>
<title>Arrays in x86 Assembly</title>
<link>https://leandronsp.com/articles/arrays-in-x86-assembly.html</link>
<guid>https://leandronsp.com/articles/arrays-in-x86-assembly.html</guid>
<description><![CDATA[<p><em>Originally posted in <a href="https://leandronsp.com/articles/arrays-em-assembly-x86-55hb">Portuguese</a></em></p>
<p>Recently I wrote <a href="https://leandronsp.com/articles/construindo-um-web-server-em-assembly-x86-parte-i-introducao-14p5">a 6-article series</a> about x86 Assembly (written in Portuguese, but I'm planning to translate the guide to English soon), covering fundamental concepts of computer architecture and low-level programming while building a minimalist multi-threaded web server.</p>
<p>During the process, I left some important concepts aside for later articles, because if I had tackled them during the series, it would have been even longer than it already was. However, these are concepts that can be addressed separately, like the queues implemented in the thread pool.</p>
<p>And when we talk about queues, <strong>it's inevitable to address arrays</strong> and how they're organized in computer memory.</p>
<p>In this article, we'll cover fundamental concepts like memory manipulation, registers, and heap memory while implementing arrays.</p>
<blockquote>
<p>I'm assuming you're already familiar with x86 Assembly and the GDB tool. If not, I strongly recommend reading my series.</p>
</blockquote>
<hr />
<h2>Agenda</h2>
<ul>
<li>
<p><a href="#arrays-dont-exist">Arrays don't exist</a></p>
</li>
<li>
<p><a href="#strings-dont-exist-either">Strings don't exist either</a></p>
</li>
<li>
<p><a href="#the-simplest-array-in-the-universe">The simplest array in the universe</a></p>
</li>
<li>
<p><a href="#using-an-array-with-uninitialized-data">Using an array with uninitialized data</a></p>
<ul>
<li><a href="#index-to-the-rescue">Index to the rescue</a></li>
<li><a href="#hitting-the-array-limit">Hitting the array limit</a></li>
</ul>
</li>
<li>
<p><a href="#heap-heap-hooray">Heap, heap, hooray!</a></p>
<ul>
<li><a href="#dynamic-memory-allocation-with-brk">Dynamic memory allocation with brk</a></li>
<li><a href="#pointers-pointers-everywhere">Pointers, pointers everywhere</a></li>
<li><a href="#resize-with-brk">Resize with brk</a></li>
</ul>
</li>
<li>
<p><a href="#the-final-program">The final program</a></p>
</li>
<li>
<p><a href="#conclusion">Conclusion</a></p>
</li>
<li>
<p><a href="#references">References</a></p>
</li>
</ul>
<hr />
<h2>Arrays don't exist</h2>
<p><em>Arrays don't exist.</em> Simple as that.</p>
<p>As we saw <a href="https://leandronsp.com/articles/construindo-um-web-server-em-assembly-x86-parte-iv-um-assembly-modesto-oif">in part IV</a> of the series, memory is organized contiguously, where information is allocated one after another.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u90q0z4ka96nccs1syef.png" alt="contiguous memory" /></p>
<p>Suppose we want to declare the following sequence of information:</p>
<pre><code>1, 2, 'H', 0
</code></pre>
<blockquote>
<p>I know, I know, the types are mixed, but that doesn't matter right now. They all fit in 1 byte</p>
</blockquote>
<p>In x86 assembly (let's call it asm for the rest of the article), we can declare this information in the data section like this:</p>
<pre><code class="language-as">section .data
stuff: db 0x1, 0x2, 0x48, 0x0
</code></pre>
<blockquote>
<p>Remember that the character 'H' in the ASCII table represents 0x48 in hexadecimal</p>
</blockquote>
<p>Using <code>gdb</code> for debugging, we can confirm that this hexadecimal sequence at the <code>stuff</code> label is stored as follows:</p>
<pre><code class="language-bash"># Reading the first hexbyte in stuff
(gdb) x/1xb (void*) &amp;stuff
0x402000 &lt;stuff&gt;:       0x01

# Reading the second hexbyte in stuff
(gdb) x/1xb (void*) &amp;stuff+1
0x402001:       0x02

# Reading the third hexbyte in stuff
(gdb) x/1xb (void*) &amp;stuff+2
0x402002:       0x48
</code></pre>
<p>We can also represent the hexadecimal <code>0x48</code> in string format using <code>x/s</code>:</p>
<pre><code class="language-bash">(gdb) x/s (void*) &amp;stuff+2
0x402002:       "H"
</code></pre>
<p><em>It's all hexadecimal!</em></p>
<p>With this, if we want to represent the string "Hello", according to the ASCII table, it could look like this:</p>
<pre><code class="language-as">section .data
msg: db 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x0
</code></pre>
<p>In gdb, let's check the string representation of the <code>msg</code> label:</p>
<pre><code class="language-bash">(gdb) x/s &amp;msg
0x402000 &lt;msg&gt;: "Hello"
</code></pre>
<p>In asm, it's possible to declare the string with direct ASCII table representation:</p>
<pre><code class="language-as">section .data
msg: db "Hello", 0x0

; same as
; msg: db 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x0
</code></pre>
<hr />
<h2>Strings don't exist either</h2>
<p>In other words, it's all hexadecimal in memory. An array, like a string, is simply a contiguous sequence of data <strong>with the same size</strong> in memory.</p>
<p>The difference is that a string is a "special array" that has data representing ASCII table characters (note that both need to delimit a "final" byte to represent the end of the string or array):</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dw5cjo8fvoh7rvp8t2l3.png" alt="string and array" /></p>
<h2>The simplest array possible</h2>
<p>Below we have the implementation of a very simple array in asm, which we'll explore step by step in subsequent sections:</p>
<pre><code class="language-as">global _start

%define SYS_exit 60
%define EXIT_SUCCESS 0

section .data
array: db 1, 2, 3, 0

section .text
_start:
	mov al, [array]        ; array[0]
	mov bl, [array + 1]    ; array[1]
	mov cl, [array + 2]    ; array[2]
	mov sil, [array + 3]   ; array[3]
.exit:
	mov rdi, EXIT_SUCCESS
	mov rax, SYS_exit
	syscall
</code></pre>
<p>In the initialized data section <code>.data</code>, we declare an array with 3 elements of 1 byte each (integers from 1 to 3), using the number 0 as the array terminator:</p>
<pre><code class="language-as">section .data
array: db 1, 2, 3, 0
</code></pre>
<p>Next, in the <code>.text</code> section, which is where the program's source code goes, we can access array elements using pointer arithmetic, storing the result in registers:</p>
<pre><code class="language-as">section .text
_start:
mov al, [array]        ; array[0]
</code></pre>
<p>In the code above, we're accessing the value contained at memory address <code>0x402000</code> and storing the result in a register (AL) that has a size of 1 byte, meaning only the first byte of the array will be stored in the register.</p>
<p>Let's check with gdb:</p>
<pre><code class="language-bash"># The array is stored at address 0x402000
# and contains the hex value 0x00 0x03 0x02 0x01,
# remembering that this architecture uses little-endian format
(gdb) x &amp;array
0x402000 &lt;array&gt;:       0x00030201

(gdb) b 13
(gdb) run
(gdb) next

# In register AL we have the first element of the array
(gdb) i r al
al             0x1                 1

# It's the same as accessing the first hexbyte contained at address
# 0x402000
(gdb) x/1xb 0x402000
0x402000 &lt;array&gt;:       0x01

</code></pre>
<blockquote>
<p>Remember that the AL register represents the lower 8-bits within the spectrum of the RAX register which encompasses a total of 64-bits in the x86_64 architecture</p>
</blockquote>
<p>To access the other array elements, just do pointer arithmetic and store in other 1-byte registers:</p>
<pre><code class="language-as">mov al, [array]        ; array[0] =&gt; 1
mov bl, [array + 1]    ; array[1] =&gt; 2
mov cl, [array + 2]    ; array[2] =&gt; 3
mov sil, [array + 3]   ; array[3] =&gt; 0 (array ends here)
</code></pre>
<hr />
<h2>Using an array with uninitialized data</h2>
<p>So far, we're declaring the array in the <code>.data</code> section where data is initialized. But we can make the program more "dynamic" by declaring the array in the section of <strong>uninitialized</strong> data, which is <code>.bss</code>.</p>
<p>Keeping compatibility with the previous example, let's declare a 4-byte array using the <code>resb</code> directive which means "reserve byte", where the first 3 bytes are reserved to store array elements and the last byte representing 0x0 which is the array terminator.</p>
<pre><code class="language-as">section .bss
array: resb 4 ; 3 bytes + 1 terminator byte
</code></pre>
<p>In gdb, we can see that the array is initialized with all values at zero, indicating that the array is empty but has 4 bytes reserved:</p>
<pre><code class="language-bash">(gdb) x &amp;array
0x402004 &lt;array&gt;:       0x00000000

(gdb) x/4xb &amp;array
0x402004 &lt;array&gt;:       0x00    0x00    0x00    0x00
</code></pre>
<p>To add elements to the array, we also need to use pointer arithmetic, just like we did in the previous example to access an array with pre-initialized data.</p>
<pre><code class="language-as">; Move value 1 to the first byte of the memory address at array
mov byte [array], 1  ; array[0] = 1
</code></pre>
<p>With gdb we confirm that at address 0x402000 where the array is, byte 1 was added:</p>
<pre><code class="language-bash">(gdb) x &amp;array
0x402000 &lt;array&gt;:       0x00000001
</code></pre>
<p>And if we want to add value 2 to the next byte of the array?</p>
<pre><code class="language-as">mov byte [array + 1], 2
</code></pre>
<pre><code class="language-bash">(gdb) x &amp;array
0x402000 &lt;array&gt;:       0x00000201
</code></pre>
<p>Notice that what changes is the array "index". At the initial position of the array, it's like the index is zero, and at the subsequent position, we use index 1, which can be incremented until the array terminator.</p>
<p>It would be very complicated to keep manipulating a hard-coded index. We need a <em>pointer</em> to represent this index.</p>
<h3>Index to the rescue</h3>
<p>Assuming the array pointer starts with <em>zero</em>, which is the memory address where the array is, we can declare it in the initialized data section <code>.data</code>:</p>
<pre><code>section .bss
array: resb 4 ; 3 bytes + 1 terminator byte

section .data
pointer: db 0
</code></pre>
<p>So, we could add the first element like this, right?</p>
<pre><code class="language-as">mov byte [array + pointer], 1   ; array + 0
</code></pre>
<p>When running the program, we get this error:</p>
<pre><code>src/live.asm:14: error: invalid effective address: multiple base segments
</code></pre>
<p><strong>This error indicates that we're trying to do pointer manipulation from multiple memory segments</strong>, in this case array and pointer.</p>
<p>To solve this, we need to do pointer manipulation with immediate values (which was the previous case with hard-coded numbers) or with registers:</p>
<pre><code class="language-as">; append(1)
mov al, byte [pointer]
mov byte [array + rax], 1   ; array + 0
</code></pre>
<ul>
<li>
<p>the first instruction moves the first byte contained at the <code>pointer</code> address and stores it in register AL</p>
</li>
<li>
<p>the second instruction moves immediate value 1 (array element) to the array's memory address. Since RAX (64-bit version of AL) has value <code>0x0</code> representing the pointer, we're inserting at the first byte of the array</p>
</li>
</ul>
<p>And to store the second element in the array?</p>
<pre><code class="language-as">; append(2)
mov al, byte [pointer]
mov byte [array + rax], 2
</code></pre>
<p>In gdb, let's check what's happening:</p>
<pre><code class="language-bash">(gdb) x &amp;array
0x402004 &lt;array&gt;:       0x00000002
</code></pre>
<p><em>Uh, oh...</em> This way we're overwriting the previous value. We actually want the pointer to "move", that is, it needs to be incremented by one byte so that <code>append(2)</code> results in 2 elements in the array.</p>
<p>With the <code>INC</code> instruction we can solve this problem:</p>
<pre><code class="language-as">mov al, byte [pointer]      ; pointer -&gt; 0
mov byte [array + rax], 1   ; array + 0
inc byte [pointer]          ; pointer -&gt; 1

mov al, byte [pointer]
mov byte [array + rax], 2   ; array + 1
</code></pre>
<pre><code class="language-bash">(gdb) x &amp;array
0x402004 &lt;array&gt;:       0x00000201
</code></pre>
<p><em>Yay!</em> What a wonderful day!</p>
<h3>Hitting the array limit</h3>
<p>And if we keep incrementing the pointer until we hit the array limit?</p>
<pre><code class="language-as">mov al, byte [pointer]
mov byte [array + rax], 1   ; array + 0
inc byte [pointer]

mov al, byte [pointer]
mov byte [array + rax], 2   ; array + 1
inc byte [pointer]

mov al, byte [pointer]
mov byte [array + rax], 3   ; array + 2
inc byte [pointer]
</code></pre>
<pre><code class="language-bash"># Reading the first 4 hexabytes of the array, we have the representation
# of the full array with all spaces occupied, remembering that
# the last byte is the array terminator
(gdb) x /4xb &amp;array
0x402004 &lt;array&gt;:       0x01    0x02    0x03    0x00

# The pointer is at the end of the array
(gdb) x &amp;pointer
0x402000 &lt;pointer&gt;:     0x03
</code></pre>
<p>Wonderful, and if we add one more element, should our program allow it?</p>
<pre><code class="language-as">mov al, byte [pointer]
mov byte [array + rax], 4   ; array + 3
inc byte [pointer]
</code></pre>
<pre><code class="language-bash"># We shouldn't allow another element to be added,
# since our array was already full
(gdb) x /4xb &amp;array
0x402004 &lt;array&gt;:       0x01    0x02    0x03    0x04

# The pointer is beyond the array capacity (not good...)
(gdb) x &amp;pointer
0x402000 &lt;pointer&gt;:     0x04
</code></pre>
<p>Let's use a conditional jump (I explain more about this in the <a href="https://leandronsp.com/articles/construindo-um-web-server-em-assembly-x86-parte-iv-um-assembly-modesto-oif">series</a>) to not allow the element to be added. With this, before appending to the array, we should check if the pointer is already at the end of the array:</p>
<pre><code class="language-as">cmp byte [pointer], 3   ; check if array is full
je .exit                ; jump to .exit routine if flag is raised
</code></pre>
<p>Here's the complete program:</p>
<pre><code class="language-as">global _start

%define SYS_exit 60
%define EXIT_SUCCESS 0

section .bss
array: resb 4 ; 3 bytes + 1 terminator byte

section .data
pointer: db 0

section .text
_start:
	cmp byte [pointer], 3   ; check if array is full
	je .exit

	mov al, byte [pointer]
	mov byte [array + rax], 1   ; array + 0
	inc byte [pointer]

	cmp byte [pointer], 3   ; check if array is full
	je .exit

	mov al, byte [pointer]
	mov byte [array + rax], 2   ; array + 1
	inc byte [pointer]

	cmp byte [pointer], 3   ; check if array is full
	je .exit

	mov al, byte [pointer]
	mov byte [array + rax], 3   ; array + 2
	inc byte [pointer]

	cmp byte [pointer], 3   ; check if array is full
	je .exit

	; shouldn't allow adding the fourth element,
	; since the array supports up to 3 elements. this way,
	; we'd be writing to the memory address of other
	; program data
	mov al, byte [pointer]
	mov byte [array + rax], 4   ; array + 3
	inc byte [pointer]
.exit:
	mov rdi, EXIT_SUCCESS
	mov rax, SYS_exit
	syscall
</code></pre>
<pre><code class="language-bash">(gdb) x &amp;pointer
0x402000 &lt;pointer&gt;:     0x00000003
(gdb) x &amp;array
0x402004 &lt;array&gt;:       0x00030201
</code></pre>
<p>Perfect, let's now do a small refactoring in the code separating the append logic into a subroutine:</p>
<pre><code class="language-as">global _start

%define SYS_exit 60
%define EXIT_SUCCESS 0
%define CAPACITY 3

section .bss
array: resb CAPACITY + 1

section .data
pointer: db 0

section .text
_start:
	mov rdi, 1
	call .append

	mov rdi, 2
	call .append

	mov rdi, 3
	call .append

	mov rdi, 4
	call .append
.exit:
	mov rdi, EXIT_SUCCESS
	mov rax, SYS_exit
	syscall
.append:
	cmp byte [pointer], CAPACITY ; check if array is full
	je .done

	mov al, byte [pointer]
	mov byte [array + rax], dil
	inc byte [pointer]
.done:
	ret
</code></pre>
<blockquote>
<p>If you want to understand more about conditional jump, routines, call, ret and flags, I suggest reading my series which has been referenced several times in this article</p>
</blockquote>
<p>Running with gdb and...</p>
<pre><code class="language-bash">(gdb) x &amp;array
0x402004 &lt;array&gt;:       0x00030201

(gdb) x &amp;pointer
0x402000 &lt;pointer&gt;:     0x00000003
</code></pre>
<p>A big <em>Yay!</em></p>
<p>However, there may be situations where we want our array to be <em>resized</em> to support more elements, that is, the array size would be dynamic.</p>
<p>How do we add more elements beyond the <em>initial capacity</em> without writing to other memory areas that don't belong to the array?</p>
<hr />
<h2>Heap, heap, hooray!</h2>
<p>Before talking about the heap, let's remember how the memory layout of a computer program works:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s81vxvgfylw0wfcf6if7.png" alt="memory layout" /></p>
<ul>
<li>
<p>the layout is represented as an area in computer memory where we have the program's lowest memory addresses toward the highest addresses at the top</p>
</li>
<li>
<p>at the lowest memory addresses, we have the <code>.text</code> section, where we've already seen that it refers to the program itself</p>
</li>
<li>
<p>then we have the <strong>data section</strong> which encompasses initialized data <code>.data</code> and the following section representing uninitialized data <code>.bss</code></p>
</li>
<li>
<p>at the highest addresses, we have the program's <em>stack</em>, which stores metadata such as the program name, its arguments and any program information that has a fixed size fitting within the stack, as well as function calls and their respective arguments</p>
</li>
<li>
<p>the stack has a <em>stack</em> format and "grows downward", that is, as we add elements to the stack, it grows toward lower addresses in memory</p>
</li>
</ul>
<p>In the "middle" of the layout, between the data section and the stack, we have a large area in memory that many end up associating as <em>heap</em>. In the heap, we can allocate data dynamically, unlike the static way we do in the data section.</p>
<p>To accommodate a dynamic-sized array that supports resizing, we have to allocate memory in this area.</p>
<blockquote>
<p>In this article, we'll call this region in the middle of memory between the data section and the stack <strong>heap</strong></p>
</blockquote>
<h3>Dynamic memory allocation with brk</h3>
<p>One way to manipulate this memory area is through the <a href="https://man7.org/linux/man-pages/man2/brk.2.html">brk syscall</a>, which changes the <em>program break</em>, which is <strong>where the data section ends</strong>.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3c703vs0c7bl1eosh5i8.png" alt="program break" /></p>
<p>With <code>brk</code>, we can modify this <em>program break</em> to higher addresses, that is, allowing manipulation of memory areas that go beyond the program and data section.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jh2x79f2nb1m1v4dcaxe.png" alt="program break visualized" /></p>
<p>The first thing we need to do is map the syscall and make the call that brings the current break address:</p>
<pre><code class="language-as">%define SYS_brk 12
....

section .text
_start:
; syscall to access the program break (0x403000), which is where
; the data section ends and the heap begins
mov rdi, 0
mov rax, SYS_brk
syscall
....
</code></pre>
<p>With gdb, let's analyze the program state:</p>
<pre><code class="language-bash"># Breakpoint at brk syscall line
(gdb) b 18
(gdb) run

# The program start is in the .text section and begins at
# 0x401000
(gdb) x _start
0x401000 &lt;_start&gt;:      0x000000bf

# The pointer is in the .data section a bit higher and starts at
# 0x402000
(gdb) x &amp;pointer
0x402000 &lt;pointer&gt;:     0x00000000

# The array is in the .bss section a bit higher and starts at
# 0x402004
(gdb) x &amp;array
0x402004 &lt;array&gt;:       0x00000000

# Execute the brk syscall
(gdb) n

# The brk syscall stores in RAX the program break memory address,
# in this case a bit higher at 0x403000
(gdb) i r rax
rax            0x403000            4206592
</code></pre>
<ul>
<li>
<p><code>0x401000</code>: <code>.text</code> section which is where the program begins</p>
</li>
<li>
<p><code>0x402000</code>: <code>.data</code> section where initialized data is</p>
</li>
<li>
<p><code>0x402004</code>: <code>.bss</code> section where uninitialized data is</p>
</li>
<li>
<p><code>0x403000</code>: program break, which is where the data section ends and our "heap" begins</p>
</li>
</ul>
<p>With this, from address <code>0x403000</code> onwards is where we'll put our array elements, so the array address can use just one byte, which points to the address where the first element begins in the heap.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mkuui4ot8ta2ohpf05bx.png" alt="array in heap" /></p>
<p>In the syscall we made, if the argument in RDI is zero, it means brk will return the current program break, in this case <code>0x403000</code>. But we can make more brk syscalls with a different RDI argument (incremented), signaling that we're changing the program break.</p>
<p>From now on, in the <code>.bss</code> data section, we no longer need to reserve 4 bytes for the array, so only 1 byte is needed which will represent the array's memory address in the heap:</p>
<pre><code class="language-as">global _start

%define SYS_brk 12
%define SYS_exit 60
%define EXIT_SUCCESS 0
%define CAPACITY 3

; initially starts with 0x000000, but will later contain
; address 0x403000
section .bss
array: resb 1

section .data
pointer: db 0

section .text
_start:
	mov rdi, 0
	mov rax, SYS_brk
	syscall

	mov rdi, rax
	add rdi, CAPACITY
	mov rax, SYS_brk
	syscall

...
...
</code></pre>
<p>Analyzing with gdb:</p>
<pre><code class="language-bash"># Breakpoint at first syscall
(gdb) b 18

(gdb) run

# Execute the syscall line
(gdb) n

# In RAX the syscall stores the program break address, in this case
# 0x403000
(gdb) i r rax
rax            0x403000            4206592

(gdb) x 0x403000
0x403000:       Cannot access memory at address 0x403000
</code></pre>
<p>At this moment, this address is not yet accessible because we haven't reserved new bytes in the heap. Let's move forward with the next syscall:</p>
<pre><code class="language-bash">(gdb) n
(gdb) n

# Before executing the syscall, we verify that argument RDI will
# contain the desired address for the new program break, in this case with
# 3 bytes added, 0x403003
(gdb) i r rdi
rdi            0x403003            4206595

# Execute the syscall...
(gdb) n
(gdb) n

# After executing the second syscall, we see that in RAX, the program break was changed to 0x403003
(gdb) i r rax
rax            0x403003            4206595
</code></pre>
<p>Now, we can access memory addresses between <code>0x403000</code> and <code>0x403003</code>:</p>
<pre><code class="language-bash">(gdb) x 0x403000
0x403000:       0x00000000
(gdb) x 0x403001
0x403001:       0x00000000
(gdb) x 0x403002
0x403002:       0x00000000
(gdb) x 0x403003
0x403003:       0x00000000
</code></pre>
<p><em>Wow!</em> Now we have in the heap a reserved area especially for our dear array, how cool is that!</p>
<p>How are we going to manipulate the array in this memory region?</p>
<h3>Pointers, pointers everywhere</h3>
<p>After the first syscall, we should take the memory address <code>0x403000</code> which represents the first program break and store it in the array pointer that's in <code>.bss</code>:</p>
<pre><code class="language-as">...
mov rdi, 0
mov rax, SYS_brk
syscall
mov [array], rax      ; &lt;---- breakpoint here

mov rdi, rax
add rdi, CAPACITY
mov rax, SYS_brk
syscall
...
</code></pre>
<p>Let's check with gdb the breakpoint at the line that changes the array pointer:</p>
<pre><code class="language-bash">(gdb) b 19
(gdb) run

(gdb) x &amp;array
0x402004 &lt;array&gt;:       0x00000000

# Execute the line that changes the pointer
(gdb) n

# Now the pointer points to address 0x403000,
# this is what we want
(gdb) x &amp;array
0x402004 &lt;array&gt;:       0x00403000
</code></pre>
<p><em>Important to note</em> that <strong>array</strong> is at address <code>0x402004</code>, in the <code>.bss</code> section, so its value represents another memory address <code>0x403000</code> which is where the first array element should start in the heap.</p>
<p>Now, when we make the next syscall to allocate 3 bytes in the heap, the program break will be modified and we'll be able to manipulate the array since the pointer already points to the correct address.</p>
<p>After the second syscall, we can no longer manipulate <code>array</code> by its value, because now the array value is no longer an actual element, but rather an address to another place in memory.</p>
<p>Here's the program in its current version:</p>
<pre><code class="language-as">global _start

%define SYS_brk 12
%define SYS_exit 60
%define EXIT_SUCCESS 0
%define CAPACITY 3

section .bss
array: resb 1   ; 0x403000

section .data
pointer: db 0

section .text
_start:
	mov rdi, 0
	mov rax, SYS_brk
	syscall
	mov [array], rax

	mov rdi, rax
	add rdi, CAPACITY
	mov rax, SYS_brk
	syscall

	mov rbx, [array]

	mov r8, 1
	call .append

	mov r8, 2
	call .append

	mov r8, 3
	call .append

	mov r8, 4
	call .append
.exit:
	mov rdi, EXIT_SUCCESS
	mov rax, SYS_exit
	syscall
.append:
	cmp byte [pointer], CAPACITY ; check if array is full
	je .done

	mov sil, byte [pointer]
	mov byte [rbx + rsi], r8b
	inc byte [pointer]
.done:
	ret
</code></pre>
<p>Explaining each block:</p>
<pre><code class="language-as">mov rdi, 0
mov rax, SYS_brk
syscall
mov [array], rax
</code></pre>
<ul>
<li>fetches the current program break and stores the address in the <code>array</code> pointer</li>
</ul>
<pre><code class="language-as">mov rdi, rax
add rdi, CAPACITY
mov rax, SYS_brk
syscall
</code></pre>
<ul>
<li>modifies the current program break, incrementing 3 bytes which is the initial array capacity in the heap</li>
</ul>
<pre><code class="language-as">; assign to the register the memory address that the
; "array" pointer is pointing to
mov rbx, [array]
</code></pre>
<ul>
<li>stores the pointer's memory address in register RBX. This is necessary because we don't want to do arithmetic directly on the pointer in the <code>.bss</code> section, but rather through a register that allows it</li>
</ul>
<pre><code class="language-as">mov r8, 1
call .append
</code></pre>
<ul>
<li>since now RDI was used as argument in the brk syscall, it's not convenient to use this register anymore to represent the element to be added to the array, so we switch to register R8</li>
</ul>
<pre><code class="language-as">.append:
	cmp byte [pointer], CAPACITY ; check if array is full
	je .done

	mov sil, byte [pointer]
	mov byte [rbx + rsi], r8b    ; indirect-mode addressing
	inc byte [pointer]
.done:
	ret
</code></pre>
<p>Now the <code>.append</code> routine has been modified so that heap array manipulation is through register RBX. We also can't use register RAX anymore to represent the pointer because the brk syscall also used it as return for the program break; in this case we switch to RSI (which has SIL as its lower 8-bit representation).</p>
<p>Running with gdb, we can verify that elements are being added at address <code>0x403000</code> which is in the heap, through the pointer that was stored in register RBX:</p>
<pre><code class="language-bash"># Array points to address 0x403000
(gdb) x &amp;array
0x402004 &lt;array&gt;:       0x00403000

# At that address, we have the added elements. Yay!
(gdb) x 0x403000
0x403000:       0x00030201

# And the "index" pointer correctly representing the end of the array in the heap
(gdb) x &amp;pointer
0x402000 &lt;pointer&gt;:     0x00000003
</code></pre>
<p>At this point, the program has the same behavior as the previous example with static array in <code>.bss</code>, not allowing adding more elements when the array reaches its limit.</p>
<p>Let's change this by resizing the array and allowing new elements to be added.</p>
<h3>Resize with brk</h3>
<p>Next, we start the steps so that array resizing is done when <strong>it reaches capacity limit</strong>. We start by changing the <code>.append</code> routine:</p>
<pre><code class="language-as">.append:
	cmp byte [pointer], CAPACITY ; check if array is full
	je .resize

	mov sil, byte [pointer]
	mov byte [rbx + rsi], r8b
	inc byte [pointer]
.done:
	ret
.resize:
	...
</code></pre>
<p>Instead of jumping to <code>.done</code> when the array is full, we jump to another subroutine called <code>.resize</code>, which should make the brk syscall again, thus modifying the <strong>program break</strong> in a new memory area, obeying the initial array capacity:</p>
<pre><code class="language-as">.append:
	cmp byte [pointer], CAPACITY ; check if array is full
	je .resize

	mov sil, byte [pointer]
	mov byte [rbx + rsi], r8b
	inc byte [pointer]
.done:
	ret
.resize:
	mov rdi, 0
	mov rax, SYS_brk
	syscall

	mov rdi, rax            ; RDI now represents the current break
	add rdi, CAPACITY       ; add 3 bytes, becoming 0x403006
	mov rax, SYS_brk
	syscall
	jmp .append
</code></pre>
<ul>
<li>
<p>the first resize syscall brings the current break, in this case we already know it's <code>0x403003</code>, which was allocated at the beginning of the program for the array</p>
</li>
<li>
<p>the second resize syscall modifies the current break, thus allocating 3 more bytes in the heap</p>
</li>
<li>
<p>at the end of resize, instead of returning the function, we'll go back to the beginning of <code>.append</code> and execute the necessary logic to add the element to the array</p>
</li>
</ul>
<p>This way, we can manipulate this new memory area to add more elements to the array, thus dynamically modifying its capacity.</p>
<p>If we run the program exactly like this, we'll face a problem, because:</p>
<ul>
<li>
<p>every time resize is done, it jumps to the beginning of the routine</p>
</li>
<li>
<p>the array size (pointer) is checked against the initial capacity, which in this case is 3. Since the pointer reached value 3, it will enter resize again characterizing an infinite loop with infinite resize until memory runs out</p>
</li>
</ul>
<p>To solve this, we need to compare the pointer with the current capacity (modified), and therefore we'll add a value in the <code>.data</code> section representing the current capacity:</p>
<pre><code class="language-as">%define CAPACITY 3

section .data
pointer: db 0
currentCapacity: db CAPACITY ; starts with 3
</code></pre>
<p>In the <code>.append</code> routine, we'll make the comparison with <code>currentCapacity</code>, which will be modified with each resize, instead of with <code>CAPACITY</code>, which will remain fixed with the initial value throughout the program.</p>
<pre><code class="language-as">.append:
	mov r9, [currentCapacity]
	cmp byte [pointer], r9b     ; check if array is full
	je .resize
...
</code></pre>
<p>And, after resizing before going back to <code>.append</code>, we'll increment the initial capacity to the current capacity:</p>
<pre><code class="language-as">.resize:
	mov rdi, 0
	mov rax, SYS_brk
	syscall

	mov rdi, rax
	add rdi, CAPACITY
	mov rax, SYS_brk
	syscall

	mov r10, currentCapacity
	add byte [r10], CAPACITY
	jmp .append
</code></pre>
<p>Running the program, we can see that element 4 was successfully added to the array after resizing:</p>
<pre><code class="language-bash">(gdb) x 0x403000
0x403000:       0x04030201
</code></pre>
<p>And if we add more and more elements?</p>
<pre><code class="language-as">...
	mov r8, 4
	call .append

	mov r8, 5
	call .append

	mov r8, 6
	call .append

	mov r8, 7
	call .append
...
</code></pre>
<pre><code class="language-bash"># We can see that currentCapacity is 9, meaning there were
# 2 resizes. Our array can now accommodate up to 9 elements,
# so when adding the tenth element, one more resize would be done.
(gdb) x &amp;currentCapacity
0x402001 &lt;currentCapacity&gt;:     0x09

# Fetching the first 9 hexabytes at the array address in the heap
(gdb) x/9xb  0x403000
0x403000:       0x01    0x02    0x03    0x04    0x05    0x06    0x07    0x00
0x403008:       0x00
</code></pre>
<p><em>How cool is that?</em></p>
<hr />
<h2>The final program</h2>
<p>Here's the final program, with an array of initial capacity of 3 elements in the heap that can be resized using the <code>brk</code> syscall, as more elements are added to the array:</p>
<pre><code class="language-as">global _start

%define SYS_brk 12
%define SYS_exit 60
%define EXIT_SUCCESS 0
%define CAPACITY 3

section .bss
array: resb 1

section .data
pointer: db 0
currentCapacity: db CAPACITY ; initial capacity is 3

section .text
_start:
	mov rdi, 0
	mov rax, SYS_brk
	syscall
	mov [array], rax

	mov rdi, rax
	add rdi, CAPACITY
	mov rax, SYS_brk
	syscall

	mov rbx, [array]

	mov r8, 1
	call .append

	mov r8, 2
	call .append

	mov r8, 3
	call .append

	mov r8, 4
	call .append

	mov r8, 5
	call .append

	mov r8, 6
	call .append

	mov r8, 7
	call .append
.exit:
	mov rdi, EXIT_SUCCESS
	mov rax, SYS_exit
	syscall
.append:
	mov r9, [currentCapacity]
	cmp byte [pointer], r9b ; check if array is full
	je .resize

	mov sil, byte [pointer]
	mov byte [rbx + rsi], r8b
	inc byte [pointer]
.done:
	ret
.resize:
	mov rdi, 0
	mov rax, SYS_brk
	syscall

	mov rdi, rax
	add rdi, CAPACITY
	mov rax, SYS_brk
	syscall

	mov r10, currentCapacity
	add byte [r10], CAPACITY
	jmp .append
</code></pre>
<hr />
<h2>Conclusion</h2>
<p>In this article, we showed the implementation of an array in x86 Assembly, covering important concepts like memory layout, register manipulation, and dynamic memory allocation with <code>brk</code>.</p>
<p>This article is the foundation for future articles about data structures, where I intend to write about implementing queues and later other data structures.</p>
<hr />
<h2>References</h2>
<ul>
<li>
<p><a href="https://man7.org/linux/man-pages/man2/brk.2.html">brk syscall</a></p>
</li>
<li>
<p><a href="https://leandronsp.com/articles/construindo-um-web-server-em-assembly-x86-parte-i-introducao-14p5">x86 Assembly series</a></p>
</li>
</ul>
]]></description>
<pubDate>2025-10-22</pubDate>
</item>
<item>
<title>You don't need Kafka: Building a message queue with only two UNIX signals</title>
<link>https://leandronsp.com/articles/you-dont-need-kafka-building-a-message-queue-with-only-two-unix-signals.html</link>
<guid>https://leandronsp.com/articles/you-dont-need-kafka-building-a-message-queue-with-only-two-unix-signals.html</guid>
<description><![CDATA[<p>Have you ever asked yourself what if we could replace any message broker with a very simple one using only two UNIX signals? Well, I'm not surprised if you didn't. But I did. And I want to share my journey of how I achieved it.</p>
<p>If you want to learn about UNIX signals, binary operations the easy way, how a message broker works under the hood, and a bit of Ruby, this post is for you.</p>
<p>And if you came here just because of the clickbait title, I apologize and invite you to keep reading. It'll be fun, I promise.</p>
<p><img src="/uploads/3491.png" alt="image" /></p>
<h2>It's all about UNIX</h2>
<p>A few days ago, I saw some discussion on the internet about how we could send messages between processes. Many people think of sockets, which are the most common way to send messages, even allowing communication across different machines and networks. Some don't even realize that pipes are another way to send messages between processes:</p>
<pre><code class="language-bash">$ echo 'hello' | base64
aGVsbG8K
</code></pre>
<p>Here's what's happening:</p>
<ul>
<li>
<p>The process <code>echo</code> is started with the content "hello"</p>
</li>
<li>
<p><code>echo</code> is a program that prints the message to <em>STDOUT</em></p>
</li>
<li>
<p>Through the pipe, the content in <em>STDOUT</em> is <strong>sent</strong> directly to the <em>STDIN</em> of the <code>base64</code> process</p>
</li>
<li>
<p>The <code>base64</code> process encodes its input to Base64 and then puts the result in <em>STDOUT</em></p>
</li>
</ul>
<p>Note the word "send". Yes, anonymous pipes are a form of <strong>IPC (Inter-process communication).</strong> Other forms of IPC in UNIX include:</p>
<ul>
<li>
<p>named pipes (mkfifo)</p>
</li>
<li>
<p>sockets</p>
</li>
<li>
<p>regular files</p>
</li>
<li>
<p>or even a simple <strong>signal</strong></p>
</li>
</ul>
<h2>UNIX signals</h2>
<p>According to <a href="url">Wikipedia</a>:</p>
<blockquote>
<p>A UNIX signal is a standardized message sent to a program to trigger specific behaviour, such as quitting or error handling</p>
</blockquote>
<p>There are many signals we can send to a process, including:</p>
<ul>
<li>
<p>SIGTERM - sends a notification to the process to terminate. It can be "trapped," which means the process can do some cleanup work before termination, like releasing OS resources and closing file descriptors</p>
</li>
<li>
<p>SIGKILL - sends a termination signal that cannot be trapped or ignored, forcing immediate termination</p>
</li>
<li>
<p>SIGINT - the interrupt signal, typically sent when you press <code>Ctrl+C</code> in the terminal. It can be trapped, allowing the process to perform cleanup before exiting gracefully</p>
</li>
<li>
<p>SIGHUP - the hangup signal, originally sent when a terminal connection was lost. Modern applications often use it to reload configuration files without restarting the process</p>
</li>
<li>
<p>SIGQUIT - similar to SIGINT but also generates a core dump for debugging</p>
</li>
<li>
<p>SIGSTOP - pauses (suspends) a process. Cannot be trapped or ignored</p>
</li>
<li>
<p>SIGCONT - resumes a process that was paused by <em>SIGSTOP</em></p>
</li>
<li>
<p>SIGCHLD - sent to a parent process when a child process terminates or stops</p>
</li>
<li>
<p><strong>SIGUSR1</strong> and <strong>SIGUSR2</strong> - user-defined signals that applications can use for custom purposes</p>
</li>
</ul>
<h2>Sending messages using  signals</h2>
<p>Okay, we know that signals are a primitive form of IPC. UNIX-like systems provide a syscall called <code>kill</code> that sends signals to processes. Historically, this syscall was created solely to terminate processes. But over time, they needed to accommodate other types of signals, so they reused the same syscall for different purposes.</p>
<p>For instance, let's create a simple Ruby script <code>sleeper.rb</code> which sleeps for 60 seconds, nothing more:</p>
<pre><code class="language-ruby">puts "Process ID: #{Process.pid}"
puts "Sleeping for 60 seconds..."
sleep 60
</code></pre>
<p>After running we see:</p>
<pre><code class="language-text">Process ID: 55402
Sleeping for 60 seconds...
</code></pre>
<p>In another window, we can <strong>send</strong> the <code>SIGTERM</code> signal to the process <code>55402</code> via syscall <code>kill</code>:</p>
<pre><code class="language-bash">$ kill -SIGTERM 55402
</code></pre>
<p>And then, in the script session:</p>
<pre><code class="language-text">[1]    55402 terminated  ruby sleeper.rb
</code></pre>
<h3>Signal traps</h3>
<p>In Ruby, we can also <em>trap</em> a signal using the <code>trap</code> method in Ruby:</p>
<pre><code class="language-ruby">puts "Process ID: #{Process.pid}"
puts "Sleeping for 60 seconds..."

trap('SIGTERM') do 
  puts "Received SIGTERM, exiting gracefully..."
  exit
end

sleep 60
</code></pre>
<p>Which in turn, after sending the signal, will gracefully:</p>
<pre><code class="language-text">Process ID: 55536
Sleeping for 60 seconds...
Received SIGTERM, exiting gracefully...
</code></pre>
<p>After all, we <em>cannot send messages using signals</em>. They are a primitive way of sending <em>standardized messages</em> which will trigger specific behaviours. At most, we can trap some signals, but nothing more.</p>
<blockquote>
<p>Okay Leandro, but what's the purpose of this article then?</p>
</blockquote>
<p><em>Hold on</em>. That's exactly why I'm here. To prove points by doing useless stuff, like when I <a href="https://leandronsp.com/articles/simulating-oop-in-bash-3mop">simulated OOP in Bash</a> a couple of years ago (it was fun though).</p>
<p>To understand how we can "hack" UNIX signals and send messages between processes, let's first talk a bit about <strong>binary operations</strong>. Yes, those "zeros" and "ones" you were scared of when you saw them for the first time. But they don't bite (🥁 LOL), I promise.</p>
<h2>What is a message?</h2>
<p>If we model a message as a sequence of characters, we could say that at a high-level, messages are simply <em>strings</em>. But in memory, they are stored as <strong>bytes</strong>.</p>
<p>We know that bytes are made of bits. In computer terms, what's a bit? It's simply an abstraction representing <strong>only two states</strong>:</p>
<ul>
<li>
<p>zero</p>
</li>
<li>
<p>one</p>
</li>
</ul>
<p>That's it. For instance, using <a href="url">ASCII</a>, we know that the letter "h" has the following codes:</p>
<ul>
<li>
<p>104 in decimal</p>
</li>
<li>
<p><code>0x68</code> in hexadecimal</p>
</li>
<li>
<p><code>01101000</code> in binary</p>
</li>
</ul>
<p>Binary-wise, what if we represented each "0" with a specific signal and each "1" with another? We know that some signals such as SIGTERM, SIGINT, and SIGCONT can be trapped, but intercepting them would harm their original purpose.</p>
<p>But thankfully, UNIX provides two user-defined signals that are perfect for our hacking experiment.</p>
<h2>Sending SIGUSR1 and SIGUSR2</h2>
<p>First things first, let's trap those signals in the code:</p>
<pre><code class="language-ruby">puts "Process ID: #{Process.pid}"
puts "Sleeping forever. Send signals to this process to see how it responds."

trap('SIGUSR1') do 
  puts "Received SIGUSR1 signal"
end

trap('SIGUSR2') do
  puts "Received SIGUSR2 signal"
end

sleep
</code></pre>
<pre><code class="language-text">Process ID: 56172
Sleeping forever. Send signals to this process to see how it responds.
</code></pre>
<p>After sending some <code>kill -SIGUSR1 56172</code> and <code>kill -SIGUSR2 56172</code>, we can see that the process prints the following content:</p>
<pre><code class="language-text">Process ID: 56172
Sleeping forever. Send signals to this process to see how it responds.
Received SIGUSR1 signal
Received SIGUSR2 signal
Received SIGUSR2 signal
Received SIGUSR1 signal
Received SIGUSR1 signal
Received SIGUSR2 signal
</code></pre>
<p><strong>Signals don't carry data</strong>. But the example we have is perfect for changing to bits, uh?</p>
<pre><code class="language-text">Received SIGUSR1 signal # 0
Received SIGUSR2 signal # 1
Received SIGUSR2 signal # 1
Received SIGUSR1 signal # 0
Received SIGUSR2 signal # 1
Received SIGUSR1 signal # 0
Received SIGUSR1 signal # 0
Received SIGUSR1 signal # 0
</code></pre>
<p>That's exactly <code>01101000</code>, the binary representation of the letter "h". We're simply <strong>encoding</strong> the letter as a binary representation and sending it via signals</p>
<p>Again, we're <strong>encoding it as a binary</strong> and sending it <strong>via signals</strong>.</p>
<p><em>How cool is that</em>?</p>
<p><img src="/uploads/3299.png" alt="image" /></p>
<h3>Decoding the binary data</h3>
<p>On the other side, the receiver should be capable of decoding the message and converting it back to the letter "h":</p>
<ul>
<li>
<p>sender <em>encodes</em> the message</p>
</li>
<li>
<p>receiver <em>decodes</em> the message</p>
</li>
</ul>
<p>So, how do we decode <code>01101000</code> (the letter "h" in ASCII)? Let's break it down into a few steps:</p>
<ol>
<li>First, we need to see the 8 bits as individual digits in their respective positions</li>
<li>The rightmost bit is at position 0, whereas the leftmost bit is at position 7. This is how we define the most significant bit (<strong>MSB</strong>, the leftmost) and the least significant bit (<strong>LSB</strong>, the rightmost)</li>
<li>For this example, we perform a <strong>left shift</strong> operation on each bit and then sum all the values, in this case from MSB to LSB (the order doesn't matter much for now): <code>(0 &lt;&lt; 7) + (1 &lt;&lt; 6) + (1 &lt;&lt; 5) + (0 &lt;&lt; 4) + ... + (0 &lt;&lt; 0)</code>:
<em>left shift on <em>zeros</em> will always produce a <em>zero</em></em></li>
</ol>
<ul>
<li>
<p><code>0 &lt;&lt; 7</code> = <code>(2 ** 7) * 0</code> = <code>128 * 0</code> = 0</p>
</li>
<li>
<p><code>1 &lt;&lt; 6</code> = <code>(2 ** 6) * 1</code> = <code>64 * 1</code> = 64</p>
</li>
</ul>
<p>Similarly to the remaining bits:</p>
<ul>
<li>
<p><code>1 &lt;&lt; 5</code> = 32</p>
</li>
<li>
<p><code>0 &lt;&lt; 4</code> = 0</p>
</li>
<li>
<p><code>1 &lt;&lt; 3</code> = 8</p>
</li>
<li>
<p><code>0 &lt;&lt; 2</code> = 0</p>
</li>
<li>
<p><code>0 &lt;&lt; 1</code> = 0</p>
</li>
<li>
<p><code>0 &lt;&lt; 0</code> = 0</p>
</li>
</ul>
<p>So, our sum becomes, from MSB to LSB:</p>
<pre><code class="language-text">MSB                          LSB
0   1    1    0   1   0   0   0
0 + 64 + 32 + 0 + 8 + 0 + 0 + 0 = 104
</code></pre>
<p>104 is exactly the <strong>decimal representation</strong> of the letter "h" in ASCII.</p>
<p><em>How wonderful is that?</em></p>
<h3>Sending the letter "h"</h3>
<p>Now let's convert these operations to Ruby code. We'll write a simple program <code>receiver.rb</code> that receives signals in order from LSB to MSB (positions 0 to 7) and then converts them back to ASCII characters, printing to <code>STDOUT</code>.</p>
<p>Basically, we'll <strong>accumulate</strong> bits and whenever we form a complete byte, we'll decode it to its ASCII representation. The very basic implementation of our <code>accumulate_bit(bit)</code> method would look like as follows:</p>
<pre><code class="language-ruby">@position = 0 # start with the LSB
@accumulator = 0

def accumulate_bit(bit)
  # The left shift operator (&lt;&lt;) is used to 
  # shift the bits of the number to the left.
  #
  # This is equivalent of: (2 ** @position) * bit
  @accumulator += (bit &lt;&lt; @position)
  return @accumulator if @position == 7 # stop accumulating after 8 bits (byte)

  @position += 1 # move to the next bit position: 0 becomes 1, 1 becomes 2, etc.
end

# Letter "h" in binary is 01101000
# But we'll send from the LSB to the MSB
#
# 0110 1000 (MSB -&gt; LSB) becomes 0001 0110 (LSB -&gt; MSB)
# The order doesn't matter that much, it'll depend on 
# the receiver's implementation.
accumulate_bit(0)
accumulate_bit(0)
accumulate_bit(0)
accumulate_bit(1)
accumulate_bit(0)
accumulate_bit(1)
accumulate_bit(1)
accumulate_bit(0)

puts @accumulator # should print 104, which is the ASCII code for "h"
</code></pre>
<p><em>Pay attention to this code. It's very important and builds the foundation for the next steps. If you didn't get it, go back and read it again. Try it yourself in the terminal or using your preferred programming language.</em></p>
<p>Now, how to convert the decimal <code>104</code> to the ASCII character representation? Luckily, Ruby provides a method called <code>chr</code> which does the job:</p>
<pre><code class="language-ruby">irb&gt; puts 104.chr
=&gt; "h"
</code></pre>
<p>We could do the same job for the rest of the word "hello", for instance. According to the <a href="https://www.ascii-code.com/">ASCII table</a>, it should be the following:</p>
<ul>
<li>
<p><code>e</code> in decimal is <code>101</code></p>
</li>
<li>
<p><code>l</code> in decimal is <code>108</code></p>
</li>
<li>
<p><code>o</code> in decimal is <code>111</code></p>
</li>
</ul>
<p>Let's check if Ruby knows that:</p>
<pre><code class="language-ruby">104.chr    # "h"
101.chr    # "e"
108.chr    # "l"
111.chr    # "o"
</code></pre>
<p>We can even "decode" the word to the decimal representation in ASCII:</p>
<pre><code class="language-ruby">irb&gt; "hello".bytes
=&gt; [104, 101, 108, 108, 111]
</code></pre>
<p>Now, time to finish our receiver implementation to properly print the letter "h":</p>
<pre><code class="language-ruby">@position = 0 # start with the LSB
@accumulator = 0

trap('SIGUSR1') { decode_signal(0) }
trap('SIGUSR2') { decode_signal(1) }

def decode_signal(bit)
  accumulate_bit(bit)
  return unless @position == 8 # if not yet accumulated a byte, keep accumulating

  print "Received byte: #{@accumulator} (#{@accumulator.chr})\n"

  @accumulator = 0 # reset the accumulator
  @position = 0 # reset position for the next byte
end

def accumulate_bit(bit)
  # The left shift operator (&lt;&lt;) is used to 
  # shift the bits of the number to the left.
  #
  # This is equivalent of: (2 ** @position) * bit
  @accumulator += (bit &lt;&lt; @position)
  @position += 1 # move to the next bit position: 0 becomes 1, 1 becomes 2, etc.
end

puts "Process ID: #{Process.pid}"
sleep
</code></pre>
<p><em>Read that code and its comments. It's very important. Do not continue reading until you really get what's happening here.</em></p>
<ul>
<li>
<p>Whenever we get <code>SIGUSR1</code>, we accumulate the bit <code>0</code></p>
</li>
<li>
<p>When getting <code>SIGUSR2</code>, accumulate then the bit <code>1</code></p>
</li>
<li>
<p>When accumulator reaches  the position<code>8</code>, it means we have a byte. At this moment we should print the ASCII representation using the <code>.chr</code> we seen earlier. Then, reset bit position and accumulator</p>
</li>
</ul>
<p>Let's see our receiver in action! Start the receiver in one terminal:</p>
<pre><code class="language-bash">$ ruby receiver.rb
Process ID: 58219
</code></pre>
<p>Great! Now the receiver is listening for signals. In another terminal, let's manually send signals
to form the letter "h" (which is <code>01101000</code> in binary, remember?):</p>
<pre><code class="language-text">  # Sending from LSB to MSB: 0, 0, 0, 1, 0, 1, 1, 0
  $ kill -SIGUSR1 58219  # 0
  $ kill -SIGUSR1 58219  # 0
  $ kill -SIGUSR1 58219  # 0
  $ kill -SIGUSR2 58219  # 1
  $ kill -SIGUSR1 58219  # 0
  $ kill -SIGUSR2 58219  # 1
  $ kill -SIGUSR2 58219  # 1
  $ kill -SIGUSR1 58219  # 0
</code></pre>
<p>And in the receiver terminal, we should see:</p>
<pre><code class="language-text">Received byte: 104 (h)
</code></pre>
<p><em>How amazing is that?</em> We just sent the letter "h" using only two UNIX signals!</p>
<p>But wait. Manually sending 8 signals for each character? That's tedious and error-prone. What if we wanted to send the word "hello"? That's 5 characters × 8 bits = 40 signals to send manually. No way.</p>
<p><em>We need a sender.</em></p>
<h3>Building the sender</h3>
<p>The sender's job is the opposite of the receiver: it should encode a message (string) into bits and send them as signals to the receiver process.</p>
<p>Let's think about what we need:</p>
<ol>
<li>Take a message as input (like "hello")</li>
<li>Convert each character to its byte representation</li>
<li>Extract the 8 bits from each byte</li>
<li>Send <code>SIGUSR1</code> for bit 0, <code>SIGUSR2</code> for bit 1</li>
<li>Repeat for all characters</li>
</ol>
<p>The tricky part here is the step 3: <strong>how do we extract individual bits from a byte?</strong> To extract the bit at position <code>i</code>, we can use the following formula:</p>
<pre><code class="language-text">bit = (byte &gt;&gt; i) &amp; 1
</code></pre>
<p>Let me break this down:</p>
<ul>
<li>
<p><code>byte &gt;&gt; i</code> performs a <em>right shift</em> by <code>i</code> positions</p>
</li>
<li>
<p><code>&amp; 1</code> is a bitwise <code>AND</code> operation that extracts only the <em>rightmost</em> bit</p>
</li>
</ul>
<p>For the letter "h" (<code>01101000</code> in binary, <code>104</code> in decimal):</p>
<p><strong>Position 0 (LSB):</strong></p>
<ul>
<li>
<p><code>(104 &gt;&gt; 0)</code> = <code>104 / (2 ** 0)</code> = <code>104 / 1</code> = 104</p>
</li>
<li>
<p><code>01101000</code> &gt;&gt; 0 = <code>01101000</code></p>
</li>
<li>
<p><code>01101000</code> &amp; <code>00000001</code> = 0 (<em>one</em> AND <em>zero</em> is <em>zero</em>)</p>
</li>
</ul>
<p><strong>Position 1:</strong></p>
<ul>
<li>
<p><code>(104 &gt;&gt; 1)</code> = <code>104 / (2 ** 1)</code> = <code>104 / 2</code> = 52</p>
</li>
<li>
<p><code>01101000</code> &gt;&gt; 1 = <code>00110100</code></p>
</li>
<li>
<p><code>00110100</code> &amp; <code>00000001</code> = 0</p>
</li>
</ul>
<p><strong>Position 2:</strong></p>
<ul>
<li>
<p><code>(104 &gt;&gt; 2)</code> = <code>104 / (2 ** 2)</code> = <code>104 / 4</code> = 26</p>
</li>
<li>
<p><code>01101000</code> &gt;&gt; 2 = <code>00011010</code></p>
</li>
<li>
<p><code>00011010</code> &amp; <code>00000001</code> = 0</p>
</li>
</ul>
<p><strong>Position 3:</strong></p>
<ul>
<li>
<p><code>(104 &gt;&gt; 3)</code> = <code>104 / (2 ** 3)</code> = <code>104 / 8</code> = 13</p>
</li>
<li>
<p><code>01101000</code> &gt;&gt; 3 = <code>00001101</code></p>
</li>
<li>
<p><code>00001101</code> &amp; <code>00000001</code> = 1 (<em>one</em> AND <em>one</em> equals <em>one</em>)</p>
</li>
</ul>
<p>And so on for positions 4, 5, 6, and 7. This gives us: <code>0, 0, 0, 1, 0, 1, 1, 0</code> — exactly the bits we need from LSB to MSB!</p>
<ul>
<li>
<p><code>(104 &gt;&gt; 0) &amp; 1</code> = <code>104 &amp; 1</code> = 0</p>
</li>
<li>
<p><code>(104 &gt;&gt; 1) &amp; 1</code> = <code>52 &amp; 1</code> = 0</p>
</li>
<li>
<p><code>(104 &gt;&gt; 2) &amp; 1</code> = <code>26 &amp; 1</code> = 0</p>
</li>
<li>
<p><code>(104 &gt;&gt; 3) &amp; 1</code> = <code>13 &amp; 1</code> = 1</p>
</li>
<li>
<p><code>(104 &gt;&gt; 4) &amp; 1</code> = <code>6 &amp; 1</code> = 0</p>
</li>
<li>
<p><code>(104 &gt;&gt; 5) &amp; 1</code> = <code>3 &amp; 1</code> = 1</p>
</li>
<li>
<p><code>(104 &gt;&gt; 6) &amp; 1</code> = <code>1 &amp; 1</code> = 1</p>
</li>
<li>
<p><code>(104 &gt;&gt; 7) &amp; 1</code> = <code>0 &amp; 1</code> = 0</p>
</li>
</ul>
<blockquote>
<p>Pay close attention to this technique. It's a fundamental operation in low-level programming.</p>
</blockquote>
<p>So now time to build the <code>sender.rb</code> which is pretty simple:</p>
<pre><code class="language-ruby">receiver_pid = ARGV[0].to_i
message = ARGV[1..-1].join(' ')

def encode_byte(byte)
  8.times.map do |i|
    # Extract each bit from the byte, starting from the LSB
    (byte &gt;&gt; i) &amp; 1
  end
end

message.bytes.each do |byte|
  encode_byte(byte).each do |bit|
    signal = bit == 0 ? 'SIGUSR1' : 'SIGUSR2'
    Process.kill(signal, receiver_pid)
    sleep 0.001 # Delay to allow the receiver to process the signal
  end
end
</code></pre>
<p>For each byte (8-bit structure) we extract the bit performing the <em>right shift</em> + <em>AND</em> oprerations. The result is the extracted bit.</p>
<p>In the receiver window:</p>
<pre><code class="language-bash">$ ruby receiver.rb
Process ID: 68968
</code></pre>
<p>And in the sender window:</p>
<pre><code class="language-bash">$ ruby sender.rb 68968 h
</code></pre>
<p>The receiver will print:</p>
<pre><code class="language-bash">$ ruby receiver.rb
Process ID: 68968
Received byte: 104 (h)
</code></pre>
<p><em>Processes sending messages with only two signals!</em> How wonderful is that?</p>
<h3>Sending the "hello" message</h3>
<p>Now, sending the hello message is super easy. The sender is already able to send not only a letter but any message using signals:</p>
<pre><code class="language-bash">$ ruby sender.rb 68968 hello

# And the receiver:
Received byte: 104 (h)
Received byte: 101 (e)
Received byte: 108 (l)
Received byte: 108 (l)
Received byte: 111 (o)
</code></pre>
<p>Just change the <code>receiver</code> implementation a little bit:</p>
<pre><code class="language-ruby">def decode_signal(bit)
  accumulate_bit(bit)
  return unless @position == 8 # if not yet accumulated a byte, keep accumulating

  print @accumulator.chr # print the byte as a character

  @accumulator = 0 # reset the accumulator
  @position = 0 # reset position for the next byte
end
</code></pre>
<p>And then:</p>
<pre><code class="language-bash">$ ruby sender.rb 96875 Hello

# In the receiver's terminal
Process ID: 96875
Hello
</code></pre>
<p>However, if we send the message again, the receiver will print everything in the same line:</p>
<pre><code class="language-ruby">$ ruby sender.rb 96875 Hello
$ ruby sender.rb 96875 Hello

# In the receiver's terminal
Process ID: 96875
HelloHello
</code></pre>
<p>It's obvious: the receiver doesn't know where the sender finished the message, so it's impossible to know where we should stop one message and print the next one on a new line with <code>\n</code>.</p>
<p>We should then determine how the sender indicates the end of the message. How about being it all <em>zeroes</em> (<code>0000 0000</code>)?</p>
<ul>
<li>
<p>We send the message: first 5 bytes representing the "hello" message</p>
</li>
<li>
<p>Then we send a "NULL terminator", just one byte <em>0</em> (<code>0000 0000</code>)</p>
</li>
</ul>
<pre><code class="language-text">0110 1000 # h
0110 0101 # e
0110 1000 # l
0110 1000 # l
0110 1111 # o
0000 0000 # NULL
</code></pre>
<p>Hence, when the <em>receiver</em> gets a NULL terminator, it will print a line feed <code>\n</code>. Let's change the <code>sender.rb</code> first:</p>
<pre><code class="language-ruby">receiver_pid = ARGV[0].to_i
message = ARGV[1..-1].join(' ')

def encode_byte(byte)
  8.times.map do |i|
    # Extract each bit from the byte, starting from the LSB
    (byte &gt;&gt; i) &amp; 1
  end
end

message.bytes.each do |byte|
  encode_byte(byte).each do |bit|
    signal = bit == 0 ? 'SIGUSR1' : 'SIGUSR2'
    Process.kill(signal, receiver_pid)
    sleep 0.001 # Delay to allow the receiver to process the signal
  end
end

# Send NULL terminator (0000 0000)
8.times do
  Process.kill('SIGUSR1', receiver_pid)
  sleep 0.001 # Delay to allow the receiver to process the signal
end

puts "Message sent to receiver (PID: #{receiver_pid})"
</code></pre>
<p>Then, the <code>receiver.rb</code>:</p>
<pre><code class="language-ruby">@position = 0 # start with the LSB
@accumulator = 0

trap('SIGUSR1') { decode_signal(0) }
trap('SIGUSR2') { decode_signal(1) }

def decode_signal(bit)
  accumulate_bit(bit)
  return unless @position == 8 # if not yet accumulated a byte, keep accumulating

  if @accumulator.zero? # NULL terminator received
    print "\n"
  else
    print @accumulator.chr # print the byte as a character
  end

  @accumulator = 0 # reset the accumulator
  @position = 0 # reset position for the next byte
end

def accumulate_bit(bit)
  # The left shift operator (&lt;&lt;) is used to 
  # shift the bits of the number to the left.
  #
  # This is equivalent of: (2 ** @position) * bit
  @accumulator += (bit &lt;&lt; @position)
  @position += 1 # move to the next bit position: 0 becomes 1, 1 becomes 2, etc.
end

puts "Process ID: #{Process.pid}"
sleep
</code></pre>
<p>Output:</p>
<pre><code class="language-text">$ ruby sender.rb 96875 Hello, World!
$ ruby sender.rb 96875 You're welcome
$ ruby sender.rb 96875 How are you?

# Receiver
Process ID: 97176
Hello, World!
You're welcome
How are you?
</code></pre>
<blockquote>
<p>OMG Leandro! That's amazing!</p>
</blockquote>
<p><em>Amazing, right?</em> We just built an entire communication system between two processes using one of the most primitive methods available: <strong>UNIX signals.</strong></p>
<p>The sky's the limit now! Why not build a <em>full-fledged message broker</em> using this crazy technique?</p>
<h2>A modest message broker using UNIX signals</h2>
<p>We'll break down the development into three components:</p>
<ol>
<li><strong>Broker</strong>: the intermediary that routes messages</li>
<li><strong>Consumer</strong>: processes that receive messages</li>
<li><strong>Producer</strong>: processes that send messages</li>
</ol>
<p><img src="/uploads/3395.png" alt="image" /></p>
<ol>
<li>Let's start with the Broker. It should register itself with the producer, then trap incoming signals, decode them, and enqueue the messages for delivery to consumers via outgoing signals:</li>
</ol>
<pre><code class="language-ruby">#!/usr/bin/env ruby

require_relative 'signal_codec'
require_relative 'consumer'

class Broker 
  PID = 'broker.pid'.freeze

  def initialize
    @codec = SignalCodec.new
    @queue = Queue.new
    @consumer_index = 0
  end

  def start 
    register_broker

    trap('SIGUSR1') { process_bit(0) }
    trap('SIGUSR2') { process_bit(1) }
    
    puts "Broker PID: #{Process.pid}"
    puts "Waiting for messages..."

    distribute_messages

    sleep # Keep alive
  end 

  private

  def process_bit(bit)
    @codec.accumulate_bit(bit) do |message|
      @queue.push(message) unless message.empty?
    end
  end

  def register_broker 
    File.write(PID, Process.pid)
    at_exit { File.delete(PID) if File.exist?(PID) }
  end

  def distribute_messages
    Thread.new do
      loop do
        sleep 0.1

        next if @queue.empty?

        consumers = File.exist?(Consumer::FILE) ? File.readlines(Consumer::FILE).map(&amp;:to_i) : []
        next if consumers.empty?

        message = @queue.pop(true) rescue next

        consumer_pid = consumers[@consumer_index % consumers.size]
        @consumer_index += 1

        puts "[SEND] #{message} → Consumer #{consumer_pid}"

        @codec.send_message(message, consumer_pid)
      end
    end
  end
end

if __FILE__ == $0 
  broker = Broker.new
  broker.start
end
</code></pre>
<ul>
<li>
<p>The broker registers itself</p>
</li>
<li>
<p>Traps incoming signals <code>USR1</code> (bit 0) and <code>USR2</code> (bit 1)</p>
</li>
<li>
<p>Enqueues the messages</p>
</li>
<li>
<p>Send messages to consumers using outgoing signals (<code>USR1</code> and <code>USR2</code> too)</p>
</li>
</ul>
<p><em>Note that we're using a module called <code>SignalCodec</code> which will be explained soon. Basically this module contains all core components to encode/decode signals and perform bitwise operations.</em></p>
<ol start="2">
<li>Now the <code>Consumer</code> implementation:</li>
</ol>
<pre><code class="language-ruby">#!/usr/bin/env ruby

require_relative 'signal_codec'

class Consumer
  FILE = 'consumers.txt'.freeze

  def initialize
    @codec = SignalCodec.new
  end

  def start
    register_consumer

    trap('SIGUSR1') { process_bit(0) }
    trap('SIGUSR2') { process_bit(1) }

    puts "Consumer PID: #{Process.pid}"
    puts "Waiting for messages..."

    sleep # Keep alive
  end

  private

  def process_bit(bit)
    @codec.accumulate_bit(bit) do |message|
      puts "[RECEIVE] #{message}"
    end
  end

  def register_consumer
    File.open(FILE, 'a') { |f| f.puts Process.pid }
    at_exit { deregister_consumer }
  end

  def deregister_consumer
    if File.exist?(FILE)
      consumers = File.readlines(FILE).map(&amp;:strip).reject { |pid| pid.to_i == Process.pid }
      File.write(FILE, consumers.join("\n"))
    end
  end
end

if __FILE__ == $0
  consumer = Consumer.new
  consumer.start
end
</code></pre>
<ul>
<li>
<p>The consumer starts and registers itself with the broker</p>
</li>
<li>
<p>Consumer then traps incoming signals (bit 0 and bit 1)</p>
</li>
<li>
<p>Decodes and prints messages</p>
</li>
</ul>
<ol start="3">
<li>Last but not least, the <code>Producer</code> implementation, which is pretty straightforward:</li>
</ol>
<pre><code class="language-ruby">#!/usr/bin/env ruby

require_relative 'signal_codec'
require_relative 'broker'

unless File.exist?(Broker::PID)
  abort "Error: Broker not running (#{Broker::PID} not found)"
end

broker_pid = File.read(Broker::PID).strip.to_i
message = ARGV.join(' ')

if message.empty?
  puts "Usage: ruby producer.rb &lt;message&gt;"
  exit 1
end

codec = SignalCodec.new

puts "Sending: #{message}"
codec.send_message(message, broker_pid)
puts "Message sent to broker (PID: #{broker_pid})"
</code></pre>
<ul>
<li>
<p>Producer receives a ASCII message from the <em>STDIN</em></p>
</li>
<li>
<p>Encode and sends the message to the broker via outgoing signals</p>
</li>
</ul>
<p>So far, this architecture should look familiar. Many broker implementations follow these basic foundations.</p>
<blockquote>
<p>Of course, production-ready implementations are far more robust than this one. Here, we're just poking around with hacking and experimentation</p>
</blockquote>
<p>The coolest part is the <code>SignalCodec</code> though:</p>
<pre><code class="language-ruby">class SignalCodec 
  SIGNAL_DELAY = 0.001 # Delay between signals to allow processing

  def initialize
    @accumulator = 0
    @position = 0
    @buffer = []
  end

  def accumulate_bit(bit)
    @accumulator += (bit &lt;&lt; @position)
    @position += 1

    if @position == 8 # Byte is complete
      if @accumulator.zero? # Message complete - NULL terminator
        decoded = @buffer.pack("C*").force_encoding('UTF-8')
        yield(decoded) if block_given?
        @buffer.clear
      else 
        @buffer &lt;&lt; @accumulator
      end

      @position = 0
      @accumulator = 0
    end
  end

  def send_message(message, pid)
    message.each_byte do |byte|
      8.times do |i|
        bit = (byte &gt;&gt; i) &amp; 1
        signal = bit == 0 ? 'SIGUSR1' : 'SIGUSR2'
        Process.kill(signal, pid)
        sleep SIGNAL_DELAY
      end
    end

    # Send NULL terminator (0000 0000)
    8.times do
      Process.kill('SIGUSR1', pid)
      sleep SIGNAL_DELAY
    end
  end
end
</code></pre>
<p>If you've been following along, this shouldn't be hard to understand, but I'll break down how this beautiful piece of code works:</p>
<ul>
<li>
<p>The codec is initialized with the bit position at zero, as well as the accumulator</p>
</li>
<li>
<p>A buffer is also initialized to store accumulated bits until a complete byte is formed</p>
</li>
<li>
<p>The <code>accumulate_bit</code> method should be familiar from our earlier implementation, but it now accepts a closure (block) that lets the caller decide what to do with each decoded byte</p>
</li>
<li>
<p><code>send_message</code> encodes a message into bits and sends them via UNIX signals</p>
</li>
</ul>
<p>Everything in action:</p>
<p><img src="/uploads/3170.png" alt="image" /></p>
<p><em>How cool, amazing, wonderful, impressive, astonishing is that?</em></p>
<h2>Conclusion</h2>
<p>Yes, we built a message broker using nothing but <strong>UNIX signals</strong> and a bit of Ruby magic. Sure, <strong>it's not production-ready</strong>, and you definitely shouldn't use this in your next startup (please don't), but that was never the point.</p>
<p>The real takeaway here isn't the broker itself: it's understanding how the fundamentals work. We explored binary operations, UNIX signals, and IPC in a hands-on way that most people never bother with.</p>
<p>We took something "useless" and made it work, just for fun. So next time someone asks you about message brokers, you can casually mention that you once built (or saw) one using just two signals. And if they look at you weird, well, that's their problem. Now go build something equally useless and amazing. The world needs more hackers who experiment just for the fun of it.</p>
<p><em>Happy hacking!</em></p>
]]></description>
<pubDate>2025-10-21</pubDate>
</item>
<item>
<title>Um resumo do meu 2024</title>
<link>https://leandronsp.com/articles/um-resumo-do-meu-2024-1lf4.html</link>
<guid>https://leandronsp.com/articles/um-resumo-do-meu-2024-1lf4.html</guid>
<description><![CDATA[<p>31 de Dezembro de 2024.</p>
<p>Sentado no sofá e assistindo Frozen, tive a ideia de escrever sobre minha retrospectiva deste ano. Nunca fiz isso antes, então bora lá, porque acho que foi muita coisa.</p>
<hr />
<h2>Mas antes, um aftermath de 2023</h2>
<p>2023 foi um ano bastante agitado pra mim. Passei por uma <a href="https://pt.wikipedia.org/wiki/Tiroidectomia">tireoidectomia</a> (mas estou bem, obrigado) e também foi o ano em que resolvi fazer "learn in public" e deixar tudo gravado no <a href="https://www.youtube.com/@leandronsp">meu canal do Youtube</a>.</p>
<p>Iniciei cobrindo a rinha de compiladores, onde submeti <a href="https://github.com/leandronsp/patropi">uma versão em Ruby</a>, e depois fui trazendo <a href="https://www.youtube.com/watch?v=6VSgMbFNUuQ">conteúdo para iniciantes</a> em Rust.
Teve também transmissão ao vivo criando uma <a href="https://www.youtube.com/watch?v=4jY_Vwnm-es">Rede Neural Artificial em Ruby</a>, então vi que eu realmente estava gostando de compartilhar minha jornada <em>coding in public</em>.</p>
<p>Na parte de artigos, <a href="https://dev.to/leandronsp">escrevi muita coisa</a> em 2023:</p>
<ul>
<li>
<p>Introdução ao <a href="https://leandronsp.com/articles/tekton-ci-part-i-a-gentle-introduction-ilj">Tekton CI/CD</a></p>
</li>
<li>
<p><a href="https://leandronsp.com/articles/kubernetes-101-part-i-the-fundamentals-23a1">Kubernetes 101</a></p>
</li>
<li>
<p>Um guia completo cobrindo <a href="https://leandronsp.com/articles/git-fundamentals-a-complete-guide-do7">os fundamentos de Git</a></p>
</li>
<li>
<p>Criando <a href="https://leandronsp.com/articles/ai-ruby-an-introduction-to-neural-networks-23f3">redes neurais em Ruby</a></p>
</li>
<li>
<p>Teve até artigo sobre <a href="https://leandronsp.com/articles/vencendo-os-numeros-de-ponto-flutuante-um-guia-de-sobrevivencia-4n7n">ponto flutuante</a></p>
</li>
<li>
<p><a href="https://leandronsp.com/articles/entendendo-fundamentos-de-recursao-2ap4">Fundamentos de recursão</a></p>
</li>
<li>
<p><a href="https://leandronsp.com/articles/compiladores-trampolim-deque-e-thread-pool-dd1">Resumo da rinha de compiladores</a> e trampolim</p>
</li>
<li>
<p><a href="https://leandronsp.com/articles/understanding-the-basics-of-smart-pointers-in-rust-3dff">Mais Rust</a></p>
</li>
</ul>
<p>E o famoso <a href="https://web101.leandronsp.com/">Guia Web 101</a> também.</p>
<blockquote>
<p>Mentira, esse guia web foi em 2021, mas eu quis colocar ele aqui só pra fazer propaganda mesmo</p>
</blockquote>
<hr />
<h2>As metas para 2024</h2>
<p>No fim de 2023 estabeleci algumas metas pra 2024 nessa parte de criação de conteúdo. Mas não foram metas muito arrojadas pois eu queria dar uma desaquecida do que foi o agitado 2023.</p>
<p>Dentre as metas estava continuar explorando Rust; escrever um <strong>guia completo de concorrência</strong>; criar um interpretador em Ruby; fazer lives com Kubernetes; falar sobre tédio e; Awk.</p>
<blockquote>
<p>Sim, Awk</p>
</blockquote>
<p>Podemos confirmar isto em meio a tantos rascunhos que tenho nesta plataforma:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/34ejih5wbhkmueyxz0qk.png" alt="Image description" /></p>
<hr />
<h2>E chegou 2024</h2>
<p>Comecei o ano de 2024 focado em me aprofundar em Rust, tanto que em Janeiro até cheguei a criar o <a href="https://github.com/leandronsp/crust">crust</a> (outro CRUD, mas desta vez em Rust) e o <a href="https://github.com/leandronsp/aspirina">aspirina</a> (outra rede neural, mas desta vez em Rust) durante algumas lives. Queria também iniciar meus estudos em Rust na parte de I/O assíncrono. Era esse o plano.</p>
<p><em>Era</em>.</p>
<h3>A Rinha de Backend, 2ª edição</h3>
<p>Mas aí veio a rinha de backend do Zan pra tirar meu foco e resolvi fazer lives compartilhando minha solução submetida, que inicialmente seria em Ruby, com o <a href="https://github.com/leandronsp/agostinho">agostinho</a>.</p>
<p><a href="https://www.youtube.com/watch?v=VR4mF9TMPws">Ledo</a> <a href="https://www.youtube.com/watch?v=5nDfz1dkX2o">engano</a>. Acabei por submeter 5 diferentes soluções:</p>
<ul>
<li>
<p>3 em Ruby: <a href="https://github.com/leandronsp/agostinho">agostinho</a>, que usa meus micro frameworks favoritos Adelnor e Chespirito; <a href="https://github.com/leandronsp/tortuga">tortuga</a> que não usa framework nem biblioteca alguma, o puro suco de uma linguagem criada no Japão; e <a href="https://github.com/leandronsp/tonico">tonico</a>, uma versão sem frameworks que usa I/O assíncrono <em>all the way down</em></p>
</li>
<li>
<p>1 em Rust: <a href="https://github.com/leandronsp/quokka">quokka</a>, criado durante uma live</p>
</li>
<li>
<p>e claro, a famosa versão em Bash, <a href="https://github.com/leandronsp/canabrava">canabrava</a></p>
</li>
</ul>
<p>Foram muitas horas em lives, inclusive esta onde eu mostrava como criar uma <a href="https://www.youtube.com/watch?v=lD3gaazwptk">thread pool e connection pool em Rust</a>.</p>
<blockquote>
<p>Nossa Leandro, como você arranja tempo pra fazer lives? Eu não consigo ter tempo pra isso</p>
</blockquote>
<p>Isso é problema seu, e não meu.</p>
<h3>Um leve sopro do Gleam e o grande tutorial de Assembly</h3>
<p>No início do ano notei um pequeno hype em cima do <strong>Gleam</strong>. Decidi <a href="https://www.youtube.com/watch?v=0XTtAra0l8Q">explorar em live</a>. Até que gostei da linguagem, e estava determinado a continuar estudando.</p>
<p>Mas aí minha amiga e meu amigo, algumas pessoas do trabalho começaram a me provocar. Ficavam colocando <strong>Assembly</strong> na minha frente. Foi quando numa 6a feira sem pretensão, no Discord, eu e mais alguns colegas fizemos um <a href="https://www.tutorialspoint.com/assembly_programming/index.htm">tutorial rápido</a> de Assembly x86.</p>
<p>Foi quando pensei "ta aí, vou aprender esse negócio e criar um web server multi-threaded simples em Assembly, compartilhando a jornada tanto em artigos quanto em lives".</p>
<h3>A saga do Assembly x86</h3>
<p>Fiz várias lives pela manhã (que eu chamava de lives matinais, duh), mostrando o desenvolvimento do web server e minha saga de aprendizado. Foi incrível, pude ter contato com pessoas como o <a href="https://www.youtube.com/@debxp">Blau Araújo</a> que é referência em conteúdo pt-BR em Assembly e outras coisas de baixo nível.</p>
<p>Tem um vídeo no meu canal, que é a minha "sincera" reação quando o server finalmente funcionou devolvendo a primeira resposta HTTP, <a href="https://www.youtube.com/watch?v=un-7IGJiXeo">ao vivo em live</a>.</p>
<p>Não obstante, resolvi também <a href="https://www.youtube.com/watch?v=bMGrJU1eRXU">escrever artigos em live</a>. Me diga, quem em sã consciência acompanha alguém escrevendo um artigo em live durante umas 4 ou 5 horas?</p>
<blockquote>
<p>Não sei quem é mais maluco</p>
</blockquote>
<p>Brincadeiras a parte, e ideia é mesmo compartilhar o processo. É sobre o <em>modus operandi</em>, a forma como eu quebro o raciocínio em partes na hora de escrever. E também o que me inspira.</p>
<p>No mundo dos artigos, eu <a href="https://leandronsp.com/articles/construindo-um-web-server-em-assembly-x86-parte-i-introducao-14p5">comecei em Abril</a> a saga "Escrevendo um Web server em Assembly x86". Foram um total de 6 artigos, onde no final conseguimos implementar um web server simples, porém multi-threaded em Assembly. Foram 3 meses escrevendo, fazendo lives e <a href="https://github.com/leandronsp/monica">muitos conceitos abordados</a>, muita coisa mesmo.</p>
<p>Aproveitei também para escrever sobre <a href="https://leandronsp.com/articles/arrays-em-assembly-x86-55hb">Arrays em Assembly x86</a>.</p>
<p>Esta saga foi muito enriquecedora pra mim. Pude aprender e firmar muitos conceitos. Vale muito a pena aprender Assembly e coisas de baixo nível.</p>
<h3>Enfim o "Guia de Concorrência 101"</h3>
<p>Quando finalizei a saga de Assembly, resolvi voltar para uma das coisas que eu tinha como meta para 2024: escrever sobre <strong>concorrência</strong>. Este é um tema que estudo há mais de 5 anos quase que diariamente, experimentando e validando conceitos.</p>
<p>Foi então que bem agora, agorinha mesmo (risos) no final de Novembro que comecei a escrever o <a href="https://concorrencia101.leandronsp.com/">guia de concorrência</a> (pt-BR). Por enquanto já abordei conceitos de concorrência no sistema operacional e como a linguagem C implementa as principais primitivas de concorrência. Mas o intuito é cobrir com mais linguagens de programação: Ruby, Python, PHP, NodeJS, Go, Rust, Elixir, Java, Kotlin e mais o que vier à cabeça.</p>
<p>Com uma brincadeira no bluesky, o <a href="https://bsky.app/profile/rdenadai.com.br">Rodolfo de Nadai</a> (meu primeiro investidor) deu uma ideia de eu lançar um "buy me a coffee" neste guia. Lancei e gostei da ideia, tanto que no momento são <a href="https://concorrencia101.leandronsp.com/agradecimentos">23 apoiadores</a> do projeto. Apesar de que faço de forma genuína sem interesse financeiro, pois defendo muito o conhecimento livre, este apoio da galera tem sido crucial para que eu continuasse, desde o apoio com Pix, revisão ou mesmo compartlihamento do conteúdo.</p>
<p>Gratidão a todos vocês que fazem isto acontecer ❤</p>
<h3>Misc</h3>
<p>Outras coisas que explorei ao longo deste ano, enquanto ia focando nas coisas de Assembly e concorrência:</p>
<ul>
<li>
<p><a href="https://github.com/leandronsp/necelu">leandronsp/necelu</a>, brincando com Lucene em Java (relembrando <em>the good old days</em>)</p>
</li>
<li>
<p><a href="https://github.com/leandronsp/otel-rails">open telemetry</a>: OTel é um assunto bem interessante, onde quero me aprofundar em 2025</p>
</li>
<li>
<p><a href="https://github.com/leandronsp/yacs">leandronsp/yacs</a>, Yet Another City Search, uma busca textual ultra-rápida em PostgreSQL em mais de 12 milhões de geonames/cidades</p>
</li>
</ul>
<hr />
<h2>O que esperar pra 2025</h2>
<p>Para 2025, espero mergulhar mais fundo em Rust, explorar OpenTelemetry e, quem sabe, encarar outra linguagem inusitada.</p>
<p>Afinal, aprender nunca é demais. 🚀</p>
<p><strong>Feliz 2025 a todes &lt;3</strong></p>
]]></description>
<pubDate>2025-10-18</pubDate>
</item>
<item>
<title>Voltando às raízes de blogueiro</title>
<link>https://leandronsp.com/articles/voltando-as-raizes-de-blogueiro.html</link>
<guid>https://leandronsp.com/articles/voltando-as-raizes-de-blogueiro.html</guid>
<description><![CDATA[<p>Já faz um tempo que não escrevo aqui no blog.</p>
<p>E também já faz bastante tempo que tenho o interesse em voltar a escrever mais como fazia antigamente, quando tudo o que tinha era algo na mente e simplesmente convertia em um punhado de texto solto no meu blog, como <a href="https://leandromaringolo.blogspot.com/">essa relíquia</a> aqui.</p>
<p>Gosto de escrever artigos técnicos, mas por outro lado também gosto de simplesmente sentar e começar a escrever sem grandes pretensões. Só que me faltava algo. Eu só não sabia o que era.</p>
<p>Para entender o que me faltava, vamos dar uns passos atrás e entender o contexto de como eu vinha utilizando plataformas de blogging e o cenário de blogging moderno.</p>
<h2>Blogpost</h2>
<p>A primeira plataforma que utilizei foi o <a href="https://www.blogger.com/">Blogger</a>, antigo <em>blogspot</em>, onde comecei a escrever meus primeiros artigos quando eu ainda "mexia com TI". <strong>O ano era 2009</strong>.</p>
<p>Eu compartilhava <a href="https://leandromaringolo.blogspot.com/">estudo de programação</a> quando tinha terminado a faculdade e também as gambiarras que eu fazia instalando e corrigindo problemas no Windows XP com Service Pack 3®.</p>
<p>Atualmente o editor é assim, mas na época era bastante limitado:</p>
<p><img src="/uploads/835.png" alt="image" /></p>
<p>Não é ruim até, mas repara como que o <strong>espaço não é aproveitado</strong>. E uma coisa que eu sempre prezo ao escrever é, no caso, o <em>conforto em escrever</em>. Gosto de uma pegada fluída, com fonte agradável e que tenha um bom contraste. No caso desta plataforma em questão, eu tenho que clicar num botão de "Preview" e assim sou direcionado para outra página, saindo completamente do contexto.</p>
<p>Isso me entristece, muito.</p>
<p>Foram bons momentos como blogueirinho no Blogger, mas fiquei uns anos sem escrever por conta de vários motivos até que...</p>
<h2>Medium</h2>
<p>Circa 2014 <a href="https://medium.com/@leandronsp">decidi experimentar</a> voltar a escrever, mas desta vez no Medium. Fiz apenas um punhado de artigos sem grandes pretensões, mas eu tinha gostado da fluidez (na época) de escrever na plataforma deles.</p>
<p>Entretanto a experiência de escrever ainda tava longe de ser a que eu queria. Mas eu nem sabia o que queria.</p>
<p><img src="/uploads/899.png" alt="image" /></p>
<blockquote>
<p>Cadê o botão de preview? Oh, gosh...</p>
</blockquote>
<p>Fast-forward para 2021.</p>
<h2>DEV.to e Hashnode</h2>
<p>A plataforma dev.to me pareceu a princípio bastante intuitiva. Tem um lance de comunidade que é bacana, porque para além de ser uma plataforma de blogging é também uma comunidade em volta disso. Sem contar com o <strong>suporte a Markdown</strong> que nos dias de hoje é crucial para escrever artigos.</p>
<p>Decidi <a href="https://dev.to/leandronsp">criar uma conta</a> lá e desde então tenho <strong>publicado quase 100 artigos</strong>:</p>
<p><img src="/uploads/931.png" alt="image" /></p>
<p>Ao mesmo tempo, a experiência de escrever era a melhor de todas que eu encontrei, mas ainda faltava uma coisa que eu já estava começando a perceber: <em>preview em tempo real</em>.</p>
<p>É muito chato você escrever bastante coisa e ter que clicar num botão lá em cima pra abrir outra página que, não raramente, demora pra carregar. É uma experiência que não sou fã 100%.</p>
<p><img src="/uploads/7650.png" alt="image" /></p>
<p>Na mesma época eu queria algo parecido mas que pudesse ter uma "landing page" no estilo dos blogs antigos com um menu customizado com os links que eu quisesse colocar. Para além do suporte a Markdown e um editor fluído. Foi quando conheci o <a href="https://hashnode.com/@leandronsp">Hashnode</a>.</p>
<p>Com Hashnode, eu pude criar uma página "customizada" com menu e assim organizar melhor meus artigos. Mas me faltava ainda algo mais configurável.</p>
<p>Pra não mencionar que a experiência de escrita continuava a ser a mesma: <em>ter que clicar num botão de preview</em> que, com uma latência absurda, faz 412 coisas menos mostrar o preview que você verdadeiramente quer naquele momento.</p>
<p><img src="/uploads/995.png" alt="image" /></p>
<p>Acabou que deixei o Hashnode flopar, mas ficou com meu domínio <code>leandronsp.com</code> e passei a escrever mais pelo DEV.to mesmo.</p>
<p><em>Era o que tinha pra janta</em>.</p>
<blockquote>
<p>Mas Leandro, existem ferramentas de visualização de Markdown, você consegue usar isso no Obsidian, na linha de comando, com plugin do Vim, com extensão do VSCode, etc etc</p>
</blockquote>
<p>Calma, calabreso. Eu quero apenas escrever, ver em tempo real o que estou escrevendo <em>sendo renderizado</em> e a seguir publicar. That's it. Pra mim o web browser é ainda a ferramenta onde sinto mais conforto ao fazer esse tipo de tarefa.</p>
<blockquote>
<p>Desculpa galera do terminal, mas como um heavy-terminal user e Vimer raiz, eu ainda prefiro blogar no browser</p>
</blockquote>
<p>Foi assim até 2 dias atrás.</p>
<h2>A vontade de escrever sobre UNIX signals</h2>
<p>Como aqueles momentos em que do nada me vem a cabeça experimentar algo inútil que ninguém pediu e que provavelmente não vai pedir, decidi criar um broker de mensageria utilizando apenas sinais UNIX.</p>
<blockquote>
<p>Em breve publico isto, foi divertido</p>
</blockquote>
<p>Então, como de praxe, resolvi sentar pra escrever sobre. No DEV.to como habitual. Mas quer saber?</p>
<p><em>Cansei</em>.</p>
<p>Resolvi criar meu próprio motor de blog que iria me atender em todos os aspectos que sinto falta nos motores atuais: aproveitar espaço, trazer <em>preview em tempo real</em>, navegação fluída, simples e sem muito ruído, tudo com suporte a Markdown.</p>
<p>Eu queria algo mais ou menos assim:</p>
<p><img src="/uploads/7778.png" alt="image" /></p>
<p>Com isto em mente, resolvi criar o <a href="https://github.com/leandronsp/curupira">curupira</a>.</p>
<h2>Curupira</h2>
<p>Pra quem já me conhece sabe que sou péssimo com nomes, mas resolvi dar esse nome em homenagem ao nosso folclore brasileiro e ao protetor das nossas matas. Só porque sim.</p>
<p>No intuito de ter uma navegação fluída como a que eu sonhava, decidi utilizar <a href="https://hexdocs.pm/phoenix_live_view/welcome.html">Phoenix LiveView</a>. Em resumo, é uma tecnologia que escala absurdamente por conta da VM que roda por baixo, a <em>BEAM</em>. Meu objetivo de ter de um lado o editor para escrever e do outro um preview em tempo real poderia ser finalmente atingido de forma simples.</p>
<p>É muito, mas muito simples criar apps real-time com Phoenix LiveView. A seguir uma demonstração de como pode ficar a página inicial do curupira:</p>
<p><img src="/uploads/484.png" alt="image" /></p>
<p>Basicamente você tem de um lado uma bio que resume teu blog, e do outro a lista de artigos. <em>É isto</em>. Não precisa ser complicado, temos que aproveitar o máximo de espaço possível na tela.</p>
<p><img src="/uploads/516.png" alt="image" /></p>
<p>Dá também pra alternar entre tema claro e escuro:</p>
<p><img src="/uploads/4098.png" alt="image" /></p>
<p>Além de tudo isso, é possível também gerar o site estático a partir de um comando <code>make</code>.</p>
<blockquote>
<p>Isto vai ser melhorado em breve, quero também deixar o processo de geração do site estático mais fluído e UX-friendly em um futuro próximo</p>
</blockquote>
<h2>leandronsp.com</h2>
<p>Após gerar o site estático, mudei o apontamento do meu domínio principal para o Cloudflare Pages, mas poderia ser qualquer outro como Github Pages, Vercel, etc (esta parte ainda está manual no momento, vem mais coisa em breve).</p>
<p>Meu novo site ficou então assim:</p>
<p><img src="/uploads/4194.png" alt="image" /></p>
<p><img src="/uploads/4258.png" alt="image" /></p>
<p>Preferi fazer com que a página do artigo tivesse espaço suficiente para o conteúdo, e não para ruídos.</p>
<hr />
<h2>Conclusão</h2>
<p>É isto, pra quem tiver curiosidade em ver o repositório <a href="https://github.com/leandronsp/curupira">leandronsp/curupira</a>, publiquei hoje mas ainda está WIP.</p>
<p>Este artigo já foi escrito com o curupira, e em breve será publicado neste blog.</p>
]]></description>
<pubDate>2025-10-18</pubDate>
</item>
<item>
<title>Arrays em Assembly x86</title>
<link>https://leandronsp.com/articles/arrays-em-assembly-x86-55hb.html</link>
<guid>https://leandronsp.com/articles/arrays-em-assembly-x86-55hb.html</guid>
<description><![CDATA[<p>Recentemente escrevi <a href="https://leandronsp.com/articles/construindo-um-web-server-em-assembly-x86-parte-i-introducao-14p5">uma saga de 6 artigos</a> sobre Assembly x86, abordando conceitos fundamentais de arquitetura de computadores e programação low-level enquanto ia desenvolvendo um web server minimalista multi-threaded.</p>
<p>Durante o processo, acabei deixando de lado alguns conceitos importantes para artigos posteriores, pois se fosse abordar durante a saga, iria ficar maior do que já foi. Entretanto são conceitos que podem ser tratados à parte, como no caso das filas implementadas na thread pool.</p>
<p>E quando falamos de filas, <strong>fica inevitável abordar arrays</strong> e como estes são organizados na memória do computador.</p>
<p>Neste artigo, vamos abordar conceitos fundamentais como manipulação de memória, registradores e memória heap ao longo da implementação de arrays.</p>
<blockquote>
<p>Vou assumir que você já tem familiaridade com Assembly x86 e a ferramenta GDB. Caso não tenha, recomendo fortemente a leitura da minha saga.</p>
</blockquote>
<hr />
<h2>Agenda</h2>
<ul>
<li>
<p><a href="#arrays-n%C3%A3o-existem">Arrays não existem</a></p>
</li>
<li>
<p><a href="#strings-tamb%C3%A9m-n%C3%A3o-existem">Strings também não existem</a></p>
</li>
<li>
<p><a href="#o-array-mais-simples-do-universo">O array mais simples do universo</a></p>
</li>
<li>
<p><a href="#utilizando-um-array-com-dados-n%C3%A3o-inicializados">Utilizando um array com dados não inicializados</a></p>
<ul>
<li><a href="#%C3%ADndice-para-o-resgate">Índice para o resgate</a></li>
<li><a href="#atingindo-o-limite-do-array">Atingindo o limite do array</a></li>
</ul>
</li>
<li>
<p><a href="#heap-heap-horray">Heap, heap, hooray!</a></p>
<ul>
<li><a href="#aloca%C3%A7%C3%A3o-din%C3%A2mica-de-mem%C3%B3ria-com-brk">Alocação dinâmica de memória com brk</a></li>
<li><a href="#ponteiros-ponteiros-everywhere">Ponteiros, ponteiros everywhere</a></li>
<li><a href="#resize-com-brk">Resize com brk</a></li>
</ul>
</li>
<li>
<p><a href="#o-programa-final">O programa final</a></p>
</li>
<li>
<p><a href="#conclus%C3%A3o">Conclusão</a></p>
</li>
<li>
<p><a href="#refer%C3%AAncias">Referências</a></p>
</li>
</ul>
<hr />
<h2>Arrays não existem</h2>
<p><em>Arrays não existem.</em> Simples assim.</p>
<p>Como vimos <a href="https://leandronsp.com/articles/construindo-um-web-server-em-assembly-x86-parte-iv-um-assembly-modesto-oif">na parte IV</a> da saga, a memória é organizada de forma contígua, onde as informações são alocadas uma após a outra.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u90q0z4ka96nccs1syef.png" alt="memoria é contígua" /></p>
<p>Supondo que queremos declarar a seguinte sequência de informações:</p>
<pre><code>1, 2, 'H', 0
</code></pre>
<blockquote>
<p>Eu sei, eu sei, os tipos estão misturados, mas isto não importa para este momento. Todos eles cabem em 1 byte</p>
</blockquote>
<p>Em assembly x86 (vamos chamar de asm no restante do artigo), podemos declarar estas informações da seguinte forma na seção de dados:</p>
<pre><code class="language-as">section .data
stuff: db 0x1, 0x2, 0x48, 0x0
</code></pre>
<blockquote>
<p>Lembrando que o caracter 'H' na tabela ASCII representa 0x48 em hexadecimal</p>
</blockquote>
<p>Ao utilizarmos o <code>gdb</code> para fazer debugging, podemos confirmar que esta sequência de hexadecimal no rótulo <code>stuff</code> está armazenada da seguinte forma:</p>
<pre><code class="language-bash"># Leitura do primeiro hexabyte em stuff
(gdb) x/1xb (void*) &amp;stuff
0x402000 &lt;stuff&gt;:       0x01

# Leitura do segundo hexabyte em stuff
(gdb) x/1xb (void*) &amp;stuff+1
0x402001:       0x02

# Leitura do terceiro hexabyte em stuff
(gdb) x/1xb (void*) &amp;stuff+2
0x402002:       0x48
</code></pre>
<p>Podemos também representar o hexadecimal <code>0x48</code> em formato de string utilizando <code>x/s</code></p>
<pre><code class="language-bash">(gdb) x/s (void*) &amp;stuff+2
0x402002:       "H"
</code></pre>
<p><em>É tudo hexadecimal!</em></p>
<p>Com isto, caso queiramos representar a string "Hello", de acordo com a tabela ASCII, poderia ficar assim:</p>
<pre><code class="language-as">section .data
msg: db 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x0
</code></pre>
<p>No gdb vamos verificar a representação string do rótulo <code>msg</code>:</p>
<pre><code class="language-bash">(gdb) x/s &amp;msg
0x402000 &lt;msg&gt;: "Hello"
</code></pre>
<p>Em asm, é possível declarar a string já com a representação direta da tabela ASCII:</p>
<pre><code class="language-as">section .data
msg: db "Hello", 0x0

; é o mesmo que
; msg: db 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x0
</code></pre>
<hr />
<h2>Strings também não existem</h2>
<p>Ou seja, é tudo hexadecimal na memória. Um array, assim como uma string, é simplesmente uma sequência contígua de dados <strong>com mesmo tamanho</strong> na memória.</p>
<p>A diferença é que a string é um "array especial" que tem dados que representam caracteres da tabela ASCII (note que ambos precisam delimitar um byte "final" para representar o término da string ou array):</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dw5cjo8fvoh7rvp8t2l3.png" alt="string e array" /></p>
<h2>O array mais simples possível</h2>
<p>A seguir temos a implementação de um array bastante simples em asm, pelo que iremos explorar cada passo nas seções subsequentes:</p>
<pre><code class="language-as">global _start

%define SYS_exit 60
%define EXIT_SUCCESS 0

section .data
array: db 1, 2, 3, 0

section .text
_start:
	mov al, [array]        ; array[0]
	mov bl, [array + 1]    ; array[1]
	mov cl, [array + 2]    ; array[2]
	mov sil, [array + 3]   ; array[3]
.exit:
	mov rdi, EXIT_SUCCESS
	mov rax, SYS_exit
	syscall
</code></pre>
<p>Na seção de dados inicializados <code>.data</code>, declaramos um array com 3 elementos de 1 byte cada (inteiros de 1 a 3), utilizando o número 0 como término do array:</p>
<pre><code class="language-as">section .data
array: db 1, 2, 3, 0
</code></pre>
<p>A seguir, na seção <code>.text</code>, que é onde vai o código fonte do programa, podemos acessar os elementos do array utilizando aritmética de ponteiros, armazenando o resultado em registradores:</p>
<pre><code class="language-as">section .text
_start:
mov al, [array]        ; array[0]
</code></pre>
<p>No código acima, estamos acessando o valor contido no endereço de memória <code>0x402000</code> e armazenando o resultado em um registrador (AL) que tem o tamanho de 1 byte, ou seja, vai ser armazenado no registrador apenas o primeiro byte do array.</p>
<p>Vamos conferir com gdb:</p>
<pre><code class="language-bash"># O array está armazenado no endereço 0x402000
# e contém o valor em hexa 0x00 0x03 0x02 0x01,
# lembrando que esta arquitetura utiliza o formato little-endian
(gdb) x &amp;array
0x402000 &lt;array&gt;:       0x00030201

(gdb) b 13
(gdb) run
(gdb) next

# No registrador AL temos o primeiro elemento do array
(gdb) i r al
al             0x1                 1

# É o mesmo que acessar o primeiro hexabyte contido no endereço
# 0x402000
(gdb) x/1xb 0x402000
0x402000 &lt;array&gt;:       0x01

</code></pre>
<blockquote>
<p>Lembrando que o registrador AL representa os 8-bits menores dentro do espectro do registrador RAX que contempla um total de 64-bits na arquitetura x86_64</p>
</blockquote>
<p>Para acessar os demais elementos do array, basta fazer aritmética de ponteiros e ir armazenando em outros registradores de 1 byte:</p>
<pre><code class="language-as">mov al, [array]        ; array[0] =&gt; 1
mov bl, [array + 1]    ; array[1] =&gt; 2
mov cl, [array + 2]    ; array[2] =&gt; 3
mov sil, [array + 3]   ; array[3] =&gt; 0 (aqui termina o array)
</code></pre>
<hr />
<h2>Utilizando um array com dados não inicializados</h2>
<p>Até agora, estamos declarando o array na seção <code>.data</code> onde os dados são inicializados. Mas podemos deixar o programa mais "dinâmico", declarando o array na seção de dados <strong>não inicializados</strong>, que é a <code>.bss</code>.</p>
<p>Mantendo a compatibilidade com o exemplo anterior, vamos declarar um array de 4 bytes utilizando a diretiva <code>resb</code> que significa "reserve byte", onde os 3 primeiros bytes são reservados para armazenar elementos do array e o último byte representando 0x0 que é o término do array.</p>
<pre><code class="language-as">section .bss
array: resb 4 ; 3 bytes + 1 byte de término
</code></pre>
<p>No gdb, podemos ver que o array está inicializado com os valores tudo a zero, o que indica que o array está vazio mas com 4 bytes reservados:</p>
<pre><code class="language-bash">(gdb) x &amp;array
0x402004 &lt;array&gt;:       0x00000000

(gdb) x/4xb &amp;array
0x402004 &lt;array&gt;:       0x00    0x00    0x00    0x00
</code></pre>
<p>Para adicionar elementos no array, precisamos também utilizar aritmética de ponteiros, tal como fizemos no exemplo anterior para acessar um array com dados pré-inicializados.</p>
<pre><code class="language-as">; Move o valor 1 para o primeiro byte do endereço de memória em array
mov byte [array], 1  ; array[0] = 1
</code></pre>
<p>Com gdb confirmamos que no endereço 0x402000 que é onde está o array, foi adicionado o byte 1:</p>
<pre><code class="language-bash">(gdb) x &amp;array
0x402000 &lt;array&gt;:       0x00000001
</code></pre>
<p>E se quisermos adicionar o valor 2 no próximo byte do array?</p>
<pre><code class="language-as">mov byte [array + 1], 2
</code></pre>
<pre><code class="language-bash">(gdb) x &amp;array
0x402000 &lt;array&gt;:       0x00000201
</code></pre>
<p>Repare que o que modifica é o "índice" do array. Na posição inicial do array, é como se o índice fosse zero, e na posição subsequente, utilizamos o índice 1, podendo incrementar até o término array.</p>
<p>Seria muito complicado ficar manipulando índice hard-coded. Precisamos de um <em>ponteiro</em> para representar este índice.</p>
<h3>Índice para o resgate</h3>
<p>Assumindo que o ponteiro do array começa com <em>zero</em>, que é o endereço de memória onde está o array, podemos declará-lo na seção de dados inicializados <code>.data</code>:</p>
<pre><code>section .bss
array: resb 4 ; 3 bytes + 1 byte de término

section .data
pointer: db 0
</code></pre>
<p>Logo, poderíamos adicionar o primeiro elemento da seguinte forma, certo?</p>
<pre><code class="language-as">mov byte [array + pointer], 1   ; array + 0
</code></pre>
<p>Ao rodar o programa, temos o seguinte erro:</p>
<pre><code>src/live.asm:14: error: invalid effective address: multiple base segments
</code></pre>
<p><strong>Este erro indica que estamos tentando fazer manipulação de ponteiros a partir de múltiplos segmentos na memória</strong>, no caso o array e pointer.</p>
<p>Para resolver isto, precisamos fazer manipulação de ponteiros com valores imediatos (que foi o caso anterior com número hard-coded) ou com registradores:</p>
<pre><code class="language-as">; append(1)
mov al, byte [pointer]
mov byte [array + rax], 1   ; array + 0
</code></pre>
<ul>
<li>
<p>a primeira instrução move o primeiro byte contido no endereço de <code>pointer</code> e armazena no registrador AL</p>
</li>
<li>
<p>a segunda instrução move o valor imediato 1 (elemento do array) para o endereço de memória do array. Como em RAX (versão 64-bits de AL) temos o valor <code>0x0</code> que representa o ponteiro, então estamos fazendo a inserção no primeiro byte do array</p>
</li>
</ul>
<p>E para armazenar o segundo elemento no array?</p>
<pre><code class="language-as">; append(2)
mov al, byte [pointer]
mov byte [array + rax], 2
</code></pre>
<p>No gdb, vamos verificar o que está acontecendo:</p>
<pre><code class="language-bash">(gdb) x &amp;array
0x402004 &lt;array&gt;:       0x00000002
</code></pre>
<p><em>Uh, oh...</em> Desta forma estamos sobrescrevendo o valor anterior. Queremos na verdade que o ponteiro "ande", ou seja, precisa ser incrementado em um byte para que o <code>append(2)</code> resulte com os 2 elementos no array.</p>
<p>Com a instrução <code>INC</code> podemos resolver este problema:</p>
<pre><code class="language-as">mov al, byte [pointer]      ; pointer -&gt; 0
mov byte [array + rax], 1   ; array + 0
inc byte [pointer]          ; pointer -&gt; 1

mov al, byte [pointer]
mov byte [array + rax], 2   ; array + 1
</code></pre>
<pre><code class="language-bash">(gdb) x &amp;array
0x402004 &lt;array&gt;:       0x00000201
</code></pre>
<p><em>Yay!</em> Que dia maravilhoso!</p>
<h3>Atingindo o limite do array</h3>
<p>E se continuarmos incrementando o ponteiro até atingir o limite do array?</p>
<pre><code class="language-as">mov al, byte [pointer]
mov byte [array + rax], 1   ; array + 0
inc byte [pointer]

mov al, byte [pointer]
mov byte [array + rax], 2   ; array + 1
inc byte [pointer]

mov al, byte [pointer]
mov byte [array + rax], 3   ; array + 2
inc byte [pointer]
</code></pre>
<pre><code class="language-bash"># Lendo os primeiros 4 hexabytes do array, temos a representação
# do array cheio com todos os espaços ocupados, lembrando que
# o último byte é o término do array
(gdb) x /4xb &amp;array
0x402004 &lt;array&gt;:       0x01    0x02    0x03    0x00

# O ponteiro está no fim do array
(gdb) x &amp;pointer
0x402000 &lt;pointer&gt;:     0x03
</code></pre>
<p>Maravilha, e se adicionar mais um elemento, nosso programa deveria permitir?</p>
<pre><code class="language-as">mov al, byte [pointer]
mov byte [array + rax], 4   ; array + 3
inc byte [pointer]
</code></pre>
<pre><code class="language-bash"># Não deveríamos permitir que mais um elemento fosse adicionado,
# pois nosso array já estava cheio
(gdb) x /4xb &amp;array
0x402004 &lt;array&gt;:       0x01    0x02    0x03    0x04

# O ponteiro está para além da capacidade array (not good...)
(gdb) x &amp;pointer
0x402000 &lt;pointer&gt;:     0x04
</code></pre>
<p>Vamos utilizar um jump condicional (explico mais sobre isto na <a href="https://leandronsp.com/articles/construindo-um-web-server-em-assembly-x86-parte-iv-um-assembly-modesto-oif">saga</a>) para não permitir que o elemento seja adicionado. Com isto, antes de fazer o append no array, devemos verificar se o ponteiro já não está no fim do array:</p>
<pre><code class="language-as">cmp byte [pointer], 3   ; verifica se o array está cheio
je .exit                ; salta para a rotina .exit caso a flag seja levantada
</code></pre>
<p>Assim fica o programa completo:</p>
<pre><code class="language-as">global _start

%define SYS_exit 60
%define EXIT_SUCCESS 0

section .bss
array: resb 4 ; 3 bytes + 1 byte de término

section .data
pointer: db 0

section .text
_start:
	cmp byte [pointer], 3   ; verifica se o array está cheio
	je .exit
	
	mov al, byte [pointer]
	mov byte [array + rax], 1   ; array + 0
	inc byte [pointer]
	
	cmp byte [pointer], 3   ; verifica se o array está cheio
	je .exit

	mov al, byte [pointer]
	mov byte [array + rax], 2   ; array + 1
	inc byte [pointer]

	cmp byte [pointer], 3   ; verifica se o array está cheio
	je .exit

	mov al, byte [pointer]
	mov byte [array + rax], 3   ; array + 2
	inc byte [pointer]

	cmp byte [pointer], 3   ; verifica se o array está cheio
	je .exit

	; não deveria permitir adicionar o quarto elemento,
	; pois o array suporta até 3 elementos. desta forma,
	; estaríamos escrevendo no endereço de memória de outros
	; dados do programa
	mov al, byte [pointer]
	mov byte [array + rax], 4   ; array + 3
	inc byte [pointer]
.exit:
	mov rdi, EXIT_SUCCESS
	mov rax, SYS_exit
	syscall
</code></pre>
<pre><code class="language-bash">(gdb) x &amp;pointer
0x402000 &lt;pointer&gt;:     0x00000003
(gdb) x &amp;array
0x402004 &lt;array&gt;:       0x00030201
</code></pre>
<p>Perfeito, vamos agora fazer um pequeno refactoring no código separando a lógica de append para uma subrotina:</p>
<pre><code class="language-as">global _start

%define SYS_exit 60
%define EXIT_SUCCESS 0
%define CAPACITY 3

section .bss
array: resb CAPACITY + 1

section .data
pointer: db 0

section .text
_start:
	mov rdi, 1
	call .append

	mov rdi, 2
	call .append

	mov rdi, 3
	call .append

	mov rdi, 4
	call .append
.exit:
	mov rdi, EXIT_SUCCESS
	mov rax, SYS_exit
	syscall
.append:
	cmp byte [pointer], CAPACITY ; verifica se o array está cheio
	je .done

	mov al, byte [pointer]
	mov byte [array + rax], dil
	inc byte [pointer]
.done:
	ret
</code></pre>
<blockquote>
<p>Se você quer entender mais sobre conditional jump, rotinas, call, ret e flags, sugiro a leitura da minha saga que foi referenciada diversas vezes neste artigo</p>
</blockquote>
<p>Executando com gdb e...</p>
<pre><code class="language-bash">(gdb) x &amp;array
0x402004 &lt;array&gt;:       0x00030201

(gdb) x &amp;pointer
0x402000 &lt;pointer&gt;:     0x00000003
</code></pre>
<p>Um grande <em>Yay!</em></p>
<p>Entretanto, podem haver situações onde queremos que nosso array seja <em>redimensionado</em> para suportar mais elementos, ou seja, o tamanho do array seria dinâmico.</p>
<p>Como adicionar mais elementos além da <em>capacidade inicial</em> de forma que não podemos escrever em outras áreas da memória que pertencem ao array?</p>
<hr />
<h2>Heap, heap, hooray!</h2>
<p>Antes de falar sobre o heap, vamos relembrar como funciona o layout de memória de um programa de computador:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s81vxvgfylw0wfcf6if7.png" alt="layout de memória" /></p>
<ul>
<li>
<p>o layout é representado como uma área na memória do computador onde temos os endereços de memória mais baixos do programa em direção aos endereços mais altos que ficam no topo</p>
</li>
<li>
<p>nos endereços de memória mais baixos, temos a seção <code>.text</code>, onde já vimos que é referente ao programa em si</p>
</li>
<li>
<p>depois temos a <strong>seção de dados</strong> que contempla os dados inicializados <code>.data</code> e a seção a seguir que representa os dados não-inicializados <code>.bss</code></p>
</li>
<li>
<p>nos endereços mais altos, temos a <em>stack</em> do programa, que armazena metadados tais como o nome do programa, seus argumentos e qualquer informação do programa que tenha um tamanho fixo cabendo dentro da stack, bem como chamadas de funções e seus respectivos argumentos</p>
</li>
<li>
<p>a stack tem um formato de <em>pilha</em> e "cresce pra baixo", ou seja, conforme adicionamos elementos na stack, esta cresce em direção aos endereços mais baixos na memória</p>
</li>
</ul>
<p>No "meio" do layout, entre a seção de dados e a stack, temos uma grande área na memória que muitos acabam associando como <em>heap</em>. No heap, podemos alocar dados de forma dinâmica, diferente da forma estática que fazemos na seção de dados.</p>
<p>Para acomodar um array de tamanho dinâmico que suporte redimensionamento (resize), temos de alocar memória nesta área.</p>
<blockquote>
<p>Neste artigo, vamos chamar esta região no meio da memória que fica entre a seção de dados e a stack de <strong>heap</strong></p>
</blockquote>
<h3>Alocação dinâmica de memória com brk</h3>
<p>Uma das formas de manipular esta área da memória é através da <a href="https://man7.org/linux/man-pages/man2/brk.2.html">syscall brk</a>, que muda o <em>program break</em>, que é <strong>onde termina a seção de dados</strong>.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3c703vs0c7bl1eosh5i8.png" alt="program break" /></p>
<p>Com <code>brk</code>, podemos modificar esse <em>program break</em> para endereços mais altos, ou seja, permitindo a manipulação de áreas na memória que vão além da seção de programa e dados.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jh2x79f2nb1m1v4dcaxe.png" alt="program break visualizado" /></p>
<p>A primeira coisa que precisamos fazer é mapear a syscall e fazer a chamada que traz o endereço do break atual:</p>
<pre><code class="language-as">%define SYS_brk 12
....

section .text
_start:
; syscall para acessar o program break (0x403000), que é onde termina 
; a seção de dados e começa o heap
mov rdi, 0
mov rax, SYS_brk
syscall
....
</code></pre>
<p>Com gdb, vamos analisar o estado do programa:</p>
<pre><code class="language-bash"># Breakpoint na linha da syscall brk
(gdb) b 18
(gdb) run

# O início do programa fica na seção .text e começa com 
# 0x401000
(gdb) x _start
0x401000 &lt;_start&gt;:      0x000000bf

# O pointer está na seção .data um pouco mais acima e começa com
# 0x402000
(gdb) x &amp;pointer
0x402000 &lt;pointer&gt;:     0x00000000

# O array está na seção .bss um pouco mais acima e começa com 
# 0x402004
(gdb) x &amp;array
0x402004 &lt;array&gt;:       0x00000000

# Executa a syscall brk
(gdb) n

# A syscall brk armazena em RAX o endereço de memória do program break, 
# no caso um pouco mais acima em 0x403000
(gdb) i r rax
rax            0x403000            4206592
</code></pre>
<ul>
<li>
<p><code>0x401000</code>: seção <code>.text</code> que é onde começa o programa</p>
</li>
<li>
<p><code>0x402000</code>: seção <code>.data</code> onde ficam os dados inicializados</p>
</li>
<li>
<p><code>0x402004</code>: seção <code>.bss</code> onde ficam os dados não-inicializados</p>
</li>
<li>
<p><code>0x403000</code>: program break, que é onde termina a seção de dados e começa o nosso "heap"</p>
</li>
</ul>
<p>Com isto, a partir do endereço <code>0x403000</code> é onde vamos colocar os elementos do nosso array, pelo que o endereço do array pode utilizar apenas um byte, que aponta para o endereço onde começa o primeiro elemento no heap.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mkuui4ot8ta2ohpf05bx.png" alt="array no heap" /></p>
<p>Na syscall que fizemos, se o argumento em RDI tiver zero, significa que brk vai retornar o program break atual, no caso <code>0x403000</code>. Mas podemos fazer mais syscalls brk com o argumento RDI diferente (incrementado), sinalizando que estamos mudando o program break.</p>
<p>A partir de agora, na seção de dados <code>.bss</code>, não precisamos mais reservar 4 bytes para o array, pelo que é necessário apenas 1 byte que irá representar o endereço de memória do array no heap:</p>
<pre><code class="language-as">global _start

%define SYS_brk 12
%define SYS_exit 60
%define EXIT_SUCCESS 0
%define CAPACITY 3

; inicialmente começa com 0x000000, mas depois irá conter 
; o endereço 0x403000
section .bss
array: resb 1  

section .data
pointer: db 0

section .text
_start:
	mov rdi, 0
	mov rax, SYS_brk
	syscall

	mov rdi, rax
	add rdi, CAPACITY
	mov rax, SYS_brk
	syscall

...
...
</code></pre>
<p>Ao analisarmos com gdb:</p>
<pre><code class="language-bash"># Breakpoint na primeira syscall
(gdb) b 18

(gdb) run

# Executa a linha da syscall
(gdb) n

# Em RAX a syscall armazena o endereço do program break, no caso
# 0x403000
(gdb) i r rax
rax            0x403000            4206592

(gdb) x 0x403000
0x403000:       Cannot access memory at address 0x403000
</code></pre>
<p>Neste momento, este endereço ainda não é acessível pois não reservamos novos bytes no heap. Vamos andar com a próxima syscall:</p>
<pre><code class="language-bash">(gdb) n
(gdb) n

# Antes de executar a syscall, verificamos que o argumento RDI vai 
# conter o endereço desejado para o novo program break, no caso com
# 3 bytes adicionados, 0x403003
(gdb) i r rdi
rdi            0x403003            4206595

# Executa a syscall...
(gdb) n
(gdb) n

# Após a execução da segunda syscall, vemos que em RAX, o program break foi alterado para 0x403003
(gdb) i r rax
rax            0x403003            4206595
</code></pre>
<p>Agora, podemos acessar os endereços de memória entre <code>0x403000</code> e <code>0x403003</code>:</p>
<pre><code class="language-bash">(gdb) x 0x403000
0x403000:       0x00000000
(gdb) x 0x403001
0x403001:       0x00000000
(gdb) x 0x403002
0x403002:       0x00000000
(gdb) x 0x403003
0x403003:       0x00000000
</code></pre>
<p><em>Uau!</em> Agora temos no heap uma área reservada especialmente para o nosso querido array, olha que coisa!</p>
<p>Como vamos manipular o array nesta região da memória?</p>
<h3>Ponteiros, ponteiros everywhere</h3>
<p>Após a primeira syscall, devemos pegar o endereço de memória <code>0x403000</code> que representa o primeiro program break e armazenar no ponteiro do array que está em <code>.bss</code>:</p>
<pre><code class="language-as">...
mov rdi, 0
mov rax, SYS_brk
syscall
mov [array], rax      ; &lt;---- breakpoint aqui

mov rdi, rax
add rdi, CAPACITY
mov rax, SYS_brk
syscall
...
</code></pre>
<p>Vamos verificar com gdb o breakpoint na linha que muda o ponteiro do array:</p>
<pre><code class="language-bash">(gdb) b 19
(gdb) run

(gdb) x &amp;array
0x402004 &lt;array&gt;:       0x00000000

# Executa a linha que muda o ponteiro
(gdb) n

# Agora o ponteiro aponta para o endereço 0x403000, 
# é isto o que queremos
(gdb) x &amp;array
0x402004 &lt;array&gt;:       0x00403000
</code></pre>
<p><em>Importante notar</em> que <strong>array</strong> está no endereço <code>0x402004</code>, na seção <code>.bss</code>, pelo que seu valor representa outro endereço de memória <code>0x403000</code> que é onde deve começar o primeiro elemento do array no heap.</p>
<p>Agora, quando fizermos a próxima syscall para alocar 3 bytes no heap, o program break será modificado e iremos conseguir manipular o array pois o ponteiro já aponta para o endereço correto.</p>
<p>Após a segunda syscall, já não podemos mais manipular o <code>array</code> pelo seu valor, pois agora o valor do array já não é mais um elemento de fato, mas sim um endereço para outro lugar na memória.</p>
<p>Vamos ao programa na versão atual:</p>
<pre><code class="language-as">global _start

%define SYS_brk 12
%define SYS_exit 60
%define EXIT_SUCCESS 0
%define CAPACITY 3

section .bss
array: resb 1   ; 0x403000

section .data
pointer: db 0

section .text
_start:
	mov rdi, 0
	mov rax, SYS_brk
	syscall
	mov [array], rax

	mov rdi, rax
	add rdi, CAPACITY
	mov rax, SYS_brk
	syscall

	mov rbx, [array]

	mov r8, 1
	call .append

	mov r8, 2
	call .append

	mov r8, 3
	call .append

	mov r8, 4
	call .append
.exit:
	mov rdi, EXIT_SUCCESS
	mov rax, SYS_exit
	syscall
.append:
	cmp byte [pointer], CAPACITY ; verifica se o array está cheio
	je .done

	mov sil, byte [pointer]
	mov byte [rbx + rsi], r8b
	inc byte [pointer]
.done:
	ret
</code></pre>
<p>Explicando cada bloco:</p>
<pre><code class="language-as">mov rdi, 0
mov rax, SYS_brk
syscall
mov [array], rax
</code></pre>
<ul>
<li>busca o program break atual e armazena o endereço no ponteiro <code>array</code></li>
</ul>
<pre><code class="language-as">mov rdi, rax
add rdi, CAPACITY
mov rax, SYS_brk
syscall
</code></pre>
<ul>
<li>modifica o program break atual, incrementando 3 bytes que é a capacidade inicial do array no heap</li>
</ul>
<pre><code class="language-as">; atribuir ao registrador o endereço de memória ao qual o
; ponteiro "array" está apontando
mov rbx, [array] 
</code></pre>
<ul>
<li>armazena o endereço de memória do ponteiro no registrador RBX. Isto é necessário pois não queremos fazer aritmética diretamente no ponteiro da seção <code>.bss</code>, mas sim através de um registrador que permite</li>
</ul>
<pre><code class="language-as">mov r8, 1
call .append
</code></pre>
<ul>
<li>como agora o RDI foi usado como argumento na syscall brk, não convém mais utilizarmos este registrador para representar o elemento a ser adicionado no array, pelo que trocamos pelo registrador R8</li>
</ul>
<pre><code class="language-as">.append:
	cmp byte [pointer], CAPACITY ; verifica se o array está cheio
	je .done

	mov sil, byte [pointer]
	mov byte [rbx + rsi], r8b    ; indirect-mode addressing
	inc byte [pointer]
.done:
	ret
</code></pre>
<p>Agora a rotina <code>.append</code> foi modificado para que a manipulação do array no heap seja através do registrador RBX. Também não podemos mais usar o registrador RAX para representar o ponteiro pois a syscall brk também utilizou como retorno do program break; neste caso trocamos para o RSI (que tem o SIL como sua representação de 8-bits menores).</p>
<p>Ao executar com gdb, podemos verificar que os elementos estão sendo adicionados no endereço <code>0x403000</code> que fica no heap, através do ponteiro que foi armazenado no registrador RBX:</p>
<pre><code class="language-bash"># Array aponta para o endereço 0x403000
(gdb) x &amp;array
0x402004 &lt;array&gt;:       0x00403000

# No endereço, temos os elementos adicionados. Yay!
(gdb) x 0x403000
0x403000:       0x00030201

# E o ponteiro de "índice" corretamente representando o fim do array no heap
(gdb) x &amp;pointer
0x402000 &lt;pointer&gt;:     0x00000003
</code></pre>
<p>Neste momento, o programa está com o mesmo comportamento do exemplo anterior com array estático em <code>.bss</code>, não permitindo adicionar mais elementos quando o array atinge seu limite.</p>
<p>Vamos mudar isto, redimensionando o array e permitir que novos elementos sejam adicionados.</p>
<h3>Resize com brk</h3>
<p>A seguir, iniciamos os passos para que o redimensionamento do array seja feito quando <strong>este atingir o limite da capacidade</strong>. Começamos por alterar a rotina <code>.append</code>:</p>
<pre><code class="language-as">.append:
	cmp byte [pointer], CAPACITY ; verifica se o array está cheio
	je .resize

	mov sil, byte [pointer]
	mov byte [rbx + rsi], r8b
	inc byte [pointer]
.done:
	ret
.resize:
	...
</code></pre>
<p>Ao invés de fazer jump para <code>.done</code> quando o array estiver cheio, fazemos jump para outra sub-rotina chamada <code>.resize</code>, que deverá fazer a syscall brk novamente, modificando assim o <strong>program break</strong> em uma nova área na memória, obedecendo a capacidade inicial do array:</p>
<pre><code class="language-as">.append:
	cmp byte [pointer], CAPACITY ; verifica se o array está cheio
	je .resize

	mov sil, byte [pointer]
	mov byte [rbx + rsi], r8b
	inc byte [pointer]
.done:
	ret
.resize:
	mov rdi, 0
	mov rax, SYS_brk
	syscall

	mov rdi, rax            ; RDI passa a representar o break atual
	add rdi, CAPACITY       ; adiciona 3 bytes, ficando 0x403006
	mov rax, SYS_brk
	syscall
	jmp .append
</code></pre>
<ul>
<li>
<p>a primeira syscall de resize traz o break atual, no caso já sabemos que é <code>0x403003</code>, que foi alocado no início do programa para o array</p>
</li>
<li>
<p>a segunda syscall de resize modifica o break atual, alocando assim mais 3 bytes no heap</p>
</li>
<li>
<p>ao fim do resize, ao invés de retornar a função, vamos voltar para o início do <code>.append</code> e executar a lógica necessária para adicionar o elemento no array</p>
</li>
</ul>
<p>Desta forma, podemos manipular esta nova área na memória para adicionar mais elementos no array, modificando assim sua capacidade dinamicamente.</p>
<p>Se executarmos o programa exatamente assim, vamos enfrentar um problema, pois:</p>
<ul>
<li>
<p>a cada vez que é feito o resize, salta para o início da rotina</p>
</li>
<li>
<p>é verificado o tamanho do array (pointeiro) com a capacidade inicial, que no caso é 3. Como o ponteiro atingiu o valor 3, então vai entrar novamente no resize caracterizando assim um loop infinito com resize infinito até acabar a memória</p>
</li>
</ul>
<p>Para resolver isto, precisamos comparar o pointer com a capacidade atual (modificada), e portanto vamos adicionar um valor na seção <code>.data</code> que representa a capacidade atual:</p>
<pre><code class="language-as">%define CAPACITY 3

section .data
pointer: db 0
currentCapacity: db CAPACITY ; começa com 3
</code></pre>
<p>Na rotina <code>.append</code>, vamos fazer a comparação com o <code>currentCapacity</code>, que vai ser modificado a cada resize, ao invés de ser com <code>CAPACITY</code>, que vai permanecer fixo com o valor inicial enquanto durar o programa.</p>
<pre><code class="language-as">.append:
	mov r9, [currentCapacity]
	cmp byte [pointer], r9b     ; verifica se o array está cheio
	je .resize
...
</code></pre>
<p>E, após o redimensionamento antes de voltar pro <code>.append</code>, vamos incrementar o valor da capacidade inicial à capacidade atual:</p>
<pre><code class="language-as">.resize:
	mov rdi, 0
	mov rax, SYS_brk
	syscall

	mov rdi, rax
	add rdi, CAPACITY
	mov rax, SYS_brk
	syscall

	mov r10, currentCapacity
	add byte [r10], CAPACITY
	jmp .append
</code></pre>
<p>Ao executar o programa, podemos ver que o elemento 4 foi adicionado com sucesso no array após o redimensionamento:</p>
<pre><code class="language-bash">(gdb) x 0x403000
0x403000:       0x04030201
</code></pre>
<p>E se adicionarmos mais e mais elementos?</p>
<pre><code class="language-as">...
	mov r8, 4
	call .append

	mov r8, 5
	call .append

	mov r8, 6
	call .append

	mov r8, 7
	call .append
...
</code></pre>
<pre><code class="language-bash"># Podemos ver que o currentCapacity é 9, ou seja, foram feitos 
# 2 redimensionamentos. Nosso array consegue agora acomodar até 9 elementos, 
# pelo que ao adicionar o décimo elemento, mais um resize seria feito.
(gdb) x &amp;currentCapacity
0x402001 &lt;currentCapacity&gt;:     0x09

# Buscando os 9 primeiros hexabytes no endereço do array no heap
(gdb) x/9xb  0x403000
0x403000:       0x01    0x02    0x03    0x04    0x05    0x06    0x07    0x00
0x403008:       0x00
</code></pre>
<p><em>How cool is that?</em></p>
<hr />
<h2>O programa final</h2>
<p>A seguir o programa final, com um array de capacidade inicial de 3 elementos no heap que pode ser redimensionado utilizando a syscall <code>brk</code>, conforme mais elementos vão sendo adicionados no array:</p>
<pre><code class="language-as">global _start

%define SYS_brk 12
%define SYS_exit 60
%define EXIT_SUCCESS 0
%define CAPACITY 3

section .bss
array: resb 1

section .data
pointer: db 0
currentCapacity: db CAPACITY ; capacidade inicial é 3

section .text
_start:
	mov rdi, 0
	mov rax, SYS_brk
	syscall
	mov [array], rax

	mov rdi, rax
	add rdi, CAPACITY
	mov rax, SYS_brk
	syscall

	mov rbx, [array]

	mov r8, 1
	call .append

	mov r8, 2
	call .append

	mov r8, 3
	call .append

	mov r8, 4
	call .append

	mov r8, 5
	call .append

	mov r8, 6
	call .append

	mov r8, 7
	call .append
.exit:
	mov rdi, EXIT_SUCCESS
	mov rax, SYS_exit
	syscall
.append:
	mov r9, [currentCapacity]
	cmp byte [pointer], r9b ; verifica se o array está cheio
	je .resize

	mov sil, byte [pointer]
	mov byte [rbx + rsi], r8b
	inc byte [pointer]
.done:
	ret
.resize:
	mov rdi, 0
	mov rax, SYS_brk
	syscall

	mov rdi, rax
	add rdi, CAPACITY
	mov rax, SYS_brk
	syscall

	mov r10, currentCapacity
	add byte [r10], CAPACITY
	jmp .append
</code></pre>
<hr />
<h2>Conclusão</h2>
<p>Neste artigo, mostramos a implementação de um array em Assembly x86, passando por conceitos importantes como layout de memória, manipulação de registradores e alocação dinâmica de memória com <code>brk</code>.</p>
<p>Este artigo é base para artigos futuros sobre estruturas de dados, onde pretendo escrever sobre a implementação de filas e posteriormente listas ligadas.</p>
<p><em>Stay tuned!</em></p>
<hr />
<h2>Referências</h2>
<sub>
Addressing modes
https://www.tutorialspoint.com/assembly_programming/assembly_addressing_modes.htm
Syscall brk
https://man7.org/linux/man-pages/man2/brk.2.html
ASCII table
https://www.asciicharstable.com/_site_media/ascii/ascii-chars-table-landscape.jpg
</sub>
]]></description>
<pubDate>2024-08-17</pubDate>
</item>
<item>
<title>Construindo um web server em Assembly x86, the grand finale, multi-threading</title>
<link>https://leandronsp.com/articles/construindo-um-web-server-em-assembly-x86-the-grand-finale-multi-threading-24hp.html</link>
<guid>https://leandronsp.com/articles/construindo-um-web-server-em-assembly-x86-the-grand-finale-multi-threading-24hp.html</guid>
<description><![CDATA[<p>Uma vez que temos um <a href="https://leandronsp.com/articles/construindo-um-web-server-em-assembly-x86-parte-v-finalmente-o-server-9e5">web server funcional</a>, podemos dar o próximo (e último) passo, que é deixar o servidor <strong>minimamente escalável</strong> fazendo uso de uma pool de threads.</p>
<p>Neste artigo, vamos mergulhar nas entranhas da implementação de uma pool de threads com sincronização através de locks, e para atingir tal feito em assembly abordaremos filas, alocação dinâmica de memória e controle de locks com futex.</p>
<p>Ao fim deste artigo, que é o último da saga, teremos uma visão mais holística sobre como funciona um web server e como uma pool de threads poderia ser implementada em linguagens de baixo nível.</p>
<p>Respira e vem comigo, esta última parte será uma avalanche de conceitos.</p>
<hr />
<h2>Agenda</h2>
<ul>
<li>
<p><a href="#simulando-a-lat%C3%AAncia-com-nanosleep">Simulando a latência com nanosleep</a></p>
</li>
<li>
<p><a href="#simulando-requests-em-escala-com-xargs">Simulando requests em escala com xargs</a></p>
</li>
<li>
<p><a href="#concorr%C3%AAncia-com-forking-de-processos">Concorrência com forking de processos</a></p>
</li>
<li>
<p><a href="#concorr%C3%AAncia-com-clone-de-processo">Concorrência com clone de processo</a></p>
</li>
<li>
<p><a href="#concorr%C3%AAncia-com-threads">Concorrência com threads</a></p>
<ul>
<li><a href="#entendendo-a-cria%C3%A7%C3%A3o-de-uma-thread">Entendendo a criação de uma thread</a></li>
<li><a href="#thread-flags">Thread flags</a></li>
<li><a href="#aloca%C3%A7%C3%A3o-de-mem%C3%B3ria-com-brk">Alocação de memória com brk</a></li>
<li><a href="#modificando-o-server-para-suportar-multi--threading">Modificando o server para suportar multi-threading</a></li>
</ul>
</li>
<li>
<p><a href="#concorr%C3%AAncia-com-thread-pool">Concorrência com thread pool</a></p>
<ul>
<li><a href="#uma-thread-em-loop">Uma thread em loop</a></li>
<li><a href="#5-threads-em-loop">5 threads em loop</a></li>
<li><a href="#sincroniza%C3%A7%C3%A3o-com-futex">Sincronização com futex</a></li>
</ul>
</li>
<li>
<p><a href="#aloca%C3%A7%C3%A3o-de-mem%C3%B3ria-com-mmap">Alocação de memória com mmap</a></p>
</li>
<li>
<p><a href="#conclus%C3%A3o">Conclusão</a></p>
</li>
<li>
<p><a href="#refer%C3%AAncias">Referências</a></p>
</li>
</ul>
<hr />
<h2>Simulando a latência com nanosleep</h2>
<p>Quando uma requisição é feita a um web server, o tempo de resposta total é um somatório de toda a latência envolvida na comunicação, desde o momento em que o pedido sai da origem (client), passando pela rede de computadores (internet), chegando no destino (server), <strong>sendo processado</strong>, para então a resposta fazer o caminho inverso até voltar ao client.</p>
<p>Quanto maior a latência em qualquer parte do processo, maior o tempo de resposta, e portanto menor a capacidade de entregar respostas de diferentes requisições em um determinado intervalo de tempo.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/89cfxfykz32gd02sdjci.png" alt="server-database" /></p>
<blockquote>
<p>A esta capacidade de processar requisições em um intervalo de tempo chamamos de <strong>throughput</strong>. O que queremos no fim das contas é <em>aumentar o throughput sem comprometer a latência</em>. Esta é uma das premissas para sistemas escaláveis, mas o foco deste artigo não será em escalabilidade necessariamente.</p>
</blockquote>
<p>No artigo anterior, finalizamos o web server que apenas responde no socket uma mensagem HTML contendo "Hello, world". A seguir o código inicial do server, que será a base para o restante do artigo:</p>
<pre><code class="language-asm">global _start

%define SYS_socket 41
%define SYS_bind 49
%define SYS_listen 50
%define SYS_accept4 288
%define SYS_write 1
%define SYS_close 3

%define AF_INET 2
%define SOCK_STREAM 1
%define SOCK_PROTOCOL 0
%define BACKLOG 2
%define CR 0xD
%define LF 0xA

section .data
sockaddr:
	sa_family: dw AF_INET   ; 2 bytes
	port: dw 0xB80B         ; 2 bytes
	ip_addr: dd 0           ; 4 bytes
	sin_zero: dq 0          ; 8 bytes
response: 
	headline: db "HTTP/1.1 200 OK", CR, LF
	content_type: db "Content-Type: text/html", CR, LF
	content_length: db "Content-Length: 22", CR, LF
	crlf: db CR, LF
	body: db "&lt;h1&gt;Hello, World!&lt;/h1&gt;"
responseLen: equ $ - response

section .bss
sockfd: resb 1

section .text
_start:
.socket:
	; int socket(int domain, int type, int protocol)
	mov rdi, AF_INET
	mov rsi, SOCK_STREAM
	mov rdx, SOCK_PROTOCOL
	mov rax, SYS_socket
	syscall
.bind:
	mov [sockfd], rax
	; int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
	mov rdi, [sockfd]
	mov rsi, sockaddr
	mov rdx, 16
	mov rax, SYS_bind
	syscall
.listen:
	; int listen(int sockfd, int backlog)
	mov rdi, [sockfd]
	mov rsi, BACKLOG
	mov rax, SYS_listen
	syscall
.accept:
	; int accept(int sockfd, struct *addr, int addrlen, int flags)
	mov rdi, [sockfd]
	mov rsi, 0
	mov rdx, 0
	mov r10, 0
	mov rax, SYS_accept4
	syscall
	mov r8, rax
	call handle
	jmp .accept
handle:
	; int write(fd)
	mov rdi, r8
	mov rsi, response
	mov rdx, responseLen
	mov rax, SYS_write
	syscall

	; int close(fd)
	mov rdi, r8
	mov rax, SYS_close
	syscall
	ret
</code></pre>
<p>Até aqui tudo normal. A rotina <code>accept</code> fica em loop chamando a rotina <code>handle</code> que escreve "Hello, world" na resposta de cada requisição que chega no socket.</p>
<p>Com <em>strace</em>, podemos ver as chamadas que foram feitas após uma requisição com <em>curl</em>:</p>
<pre><code class="language-bash">socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3
bind(3, {sa_family=AF_INET, sin_port=htons(3000), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
listen(3, 2)                            = 0
accept4(3, NULL, NULL, 0)               = 4
write(4, "HTTP/1.1 200 OK\r\nContent-Type: t"..., 86) = 86
close(4)                                = 0
accept4(3, NULL, NULL, 0
</code></pre>
<blockquote>
<p><strong>socket</strong>, bind, listen, para então iniciar o <strong>accept</strong>, que ao receber uma requisição HTTP, passa para <strong>write</strong>, <strong>close</strong> e então voltar ao <strong>accept</strong> novamente em loop.</p>
</blockquote>
<p>Para simular um pouco de latência, vamos fazer com que a resposta demore cerca de 1 segundo, e para tanto precisamos utilizar uma syscall no Linux chamada <code>nanosleep</code>, que suspende a execução da thread atual até atingir um tempo decorrido especificado com base no relógio monotônico do sistema:</p>
<p>Primeiro definimos a syscall, que tem o código 35:</p>
<pre><code class="language-as">%define SYS_nanosleep 35
</code></pre>
<p>Na rotina <code>handle</code>, antes de escrever a resposta no socket, fazemos a chamada de sistema para <strong>nanosleep</strong> passando como argumento uma struct que representa um <em>timespec</em>, que contempla o tempo decorrido em segundos e nano-segundos:</p>
<pre><code class="language-as">handle:
	; int nanosleep(timespec duration)
	lea rdi, [timespec]
	mov rax, SYS_nanosleep
	syscall

	; int write(fd)
	...

	; int close(fd)
	...
</code></pre>
<p>E na seção de dados, definimos o tempo decorrido em segundos, que são os primeiros 8 bytes da struct, deixando a <em>zero</em> os 8 bytes restantes que representam o tempo em nano-segundos</p>
<pre><code class="language-as">section .data
timespec:
	tv_sec: dq 1
	tv_nsec: dq 0
</code></pre>
<blockquote>
<p>Neste exemplo queremos que o sleep seja de 1 segundo</p>
</blockquote>
<p>Com <em>strace</em>, podemos ver que a syscall <code>nanosleep</code> foi executada após o <strong>accept</strong> e antes do <strong>write</strong>:</p>
<pre><code class="language-bash">socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3
bind(3, {sa_family=AF_INET, sin_port=htons(3000), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
listen(3, 2)                            = 0

accept4(3, NULL, NULL, 0)               = 4
nanosleep({tv_sec=1, tv_nsec=0}, NULL)  = 0
write(4, "HTTP/1.1 200 OK\r\nContent-Type: t"..., 86) = 86

close(4)                                = 0
accept4(3, NULL, NULL, 0
</code></pre>
<p>Calculando o tempo decorrido com o utilitário <code>time</code>:</p>
<pre><code class="language-bash">$ time curl localhost:3000

&lt;h1&gt;Hello, World!&lt;/h1&gt;
real    0m1.040s
user    0m0.005s
sys     0m0.009s
</code></pre>
<p>Podemos também encurtar a resposta do time trazendo apenas o tempo real, exportando a variável na sessão shell atual ou adicionando no <code>bashrc</code>:</p>
<pre><code class="language-bash">export TIMEFORMAT=%R

$ time curl localhost:3000
&lt;h1&gt;Hello, World!&lt;/h1&gt;1.036
</code></pre>
<p><em>Yay!</em> Já conseguimos simular uma latência de 1 segundo em Assembly. Agora vamos ver se nosso web server tem a capacidade de atender a <strong>requests em escala</strong>.</p>
<hr />
<h2>Simulando requests em escala com xargs</h2>
<p>Para começar, vamos simular 10 requests sequenciais com curl. Poderíamos ficar digitando <code>curl localhost:3000</code> 10 vezes, ou então ser pragmáticos, automatizar sem reinventar a roda e nem instalar nada adicional no sistema.</p>
<blockquote>
<p>Como?</p>
</blockquote>
<p><em>xargs.</em></p>
<p><strong>xargs</strong> é um utilitário presente na maioria dos sistemas operacionais UNIX-like, que lê strings a partir de arquivos ou standard input e utiliza estas strings como argumentos para comandos arbitrários.</p>
<p>Vamos ter como exemplo uma sequência de 1 a 10 em bash:</p>
<pre><code class="language-bash">$ echo ${1..10}
1 2 3 4 5 6 7 8 9 10
</code></pre>
<p>Podemos utilizar cada valor do <strong>echo</strong> como argumento para o xargs:</p>
<pre><code class="language-bash">$ echo {1..10} | xargs -n1
1
2
3
4
5
6
7
8
9
10
</code></pre>
<p>A opção <code>-n1</code> significa a quantidade de argumentos que serão usados para o comando que vem a seguir ao xargs, que no caso queremos apenas 1 argumento, o que neste caso tanto faz pois não queremos fazer nada com o argumento: queremos apenas executar o comando <strong>curl</strong> 10 vezes.</p>
<p>Podemos então agora executar o <strong>curl</strong> com o time para saber o tempo decorrido de cada request:</p>
<pre><code class="language-bash">$ time echo {1..10} | xargs -n1 bash -c "time curl localhost:3000"

&lt;h1&gt;Hello, World!&lt;/h1&gt;1.037
&lt;h1&gt;Hello, World!&lt;/h1&gt;1.033
&lt;h1&gt;Hello, World!&lt;/h1&gt;1.025
&lt;h1&gt;Hello, World!&lt;/h1&gt;1.037
&lt;h1&gt;Hello, World!&lt;/h1&gt;1.032
&lt;h1&gt;Hello, World!&lt;/h1&gt;1.026
&lt;h1&gt;Hello, World!&lt;/h1&gt;1.019
&lt;h1&gt;Hello, World!&lt;/h1&gt;1.046
&lt;h1&gt;Hello, World!&lt;/h1&gt;1.053
&lt;h1&gt;Hello, World!&lt;/h1&gt;1.041
10.426
</code></pre>
<p>Claramente, vemos que cada request demorou cerca de 1 segundo, o que no total o tempo decorrido foi de <strong>10,4</strong> segundos. Esta é a latência total para o caso de fazermos requisições sequenciais.</p>
<p>E se fizermos <strong>requisições simultâneas</strong>? Num cenário mais próximo do real, vamos supor que nossa aplicação web recebe 10 requisições no mesmo segundo em horários de pico.</p>
<p>Para isto, conseguimos também utilizar o <strong>xargs</strong> para simular, através da opção <code>-P</code>, que representa a quantidade de processos simultâneos que o xargs irá utilizar para realizar os comandos.</p>
<blockquote>
<p>Incrível! Com isto nosso web server atende 10 requisições simultâneas, fazendo com que o throughput total dos 10 requests fique em torno de 1 segundo, certo?</p>
</blockquote>
<p><em>Calma, calabreso</em>, vamos testar.</p>
<pre><code class="language-bash">$ time echo {1..10} | xargs -n1 -P10 bash -c "time curl localhost:3000"

&lt;h1&gt;Hello, World!&lt;/h1&gt;1.053
&lt;h1&gt;Hello, World!&lt;/h1&gt;2.071
&lt;h1&gt;Hello, World!&lt;/h1&gt;3.076
&lt;h1&gt;Hello, World!&lt;/h1&gt;4.087
&lt;h1&gt;Hello, World!&lt;/h1&gt;5.088
&lt;h1&gt;Hello, World!&lt;/h1&gt;6.106
&lt;h1&gt;Hello, World!&lt;/h1&gt;7.140
&lt;h1&gt;Hello, World!&lt;/h1&gt;8.154
&lt;h1&gt;Hello, World!&lt;/h1&gt;9.168
&lt;h1&gt;Hello, World!&lt;/h1&gt;10.183
10.214
</code></pre>
<p><strong>Não melhorou nada!</strong> Ter 10 requests simultâneos não quer dizer que nosso server consiga atender os 10 requests ao mesmo tempo. Muito pelo contrário, pode até piorar e prejudicar a latência total, pois há diversos requests na fila esperando para serem atendidos.</p>
<ul>
<li>
<p>o primeiro request demora 1 segundo</p>
</li>
<li>
<p>o segundo request chega ao mesmo tempo mas demora 2 segundos</p>
</li>
<li>
<p>o terceiro request chega ao mesmo tempo mas demora 3 segundos</p>
</li>
<li>
<p>e assim sucessivamente...</p>
</li>
</ul>
<p>Nosso server é síncrono, e com isto podemos criar gargalos. Precisamos então que o server consiga lidar com <strong>concorrência</strong>.</p>
<hr />
<h2>Concorrência com forking de processos</h2>
<p>Uma das formas primitivas de concorrência e escalar um web server para atender mais de um request em simultâneo é com o uso de <strong>processos</strong>. Como cada processo no sistema operacional tem sua <em>memória isolada</em> dos demais, podemos fazer com que cada request seja atendido em um processo diferente.</p>
<p>Para entender esta técnica, precisamos compreender que <em>todo programa de computador</em> roda em um <strong>processo</strong> no sistema operacional, e isto vimos bastante nos artigos anteriores. Dentro deste processo, o programa ainda roda em uma unidade de execução no SO chamada <strong>thread</strong>.</p>
<blockquote>
<p>Todo processo tem uma thread chamada <em>thread principal</em>, que é onde está sendo executado o programa</p>
</blockquote>
<p>No exemplo anterior, quando chamamos o <em>sleep</em>, a thread que está sendo suspensa por um tempo determinado é justamente a <strong>thread principal</strong> do programa.</p>
<p>A thread compartilha a memória do processo o qual ela faz parte, mas como precisamos criar <strong>outro processo</strong>, temos de fazer um <em>forking</em>, que basicamente <em>cria um processo filho copiando tudo</em> o que o programa principal tem.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fhz3hnrjktfwv33zqdpv.png" alt="forking de processos" /></p>
<p>Repare que cada processo filho tem uma cópia do processo principal. O loop é basicamente o <strong>accept</strong> do nosso web server, que fica em loop. Desta forma cada request pode ser atendido por um processo diferente, <strong>de forma concorrente</strong>.</p>
<p>Podemos fazer forking de processo com o uso da syscall <em>fork</em>:</p>
<pre><code class="language-as">%define SYS_fork 57
</code></pre>
<p>A rotina <em>handle</em> mantém igual, com o sleep antes de escrever a resposta no socket:</p>
<pre><code class="language-as">handle:
	lea rdi, [timespec]
	mov rax, SYS_nanosleep
	syscall

	; int write(fd)
	mov rdi, r8
	mov rsi, response
	mov rdx, responseLen
	mov rax, SYS_write
	syscall

	; int close(fd)
	mov rdi, r8
	mov rax, SYS_close
	syscall
	ret
</code></pre>
<p>E na rotina <strong>accept</strong>, adicionamos a chamada do fork logo após o request chegar no socket:</p>
<pre><code class="language-as">.accept:
	; int accept(int sockfd, struct *addr, int addrlen, int flags)
	mov rdi, [sockfd]
	mov rsi, 0
	mov rdx, 0
	mov r10, 0
	mov rax, SYS_accept4
	syscall
	mov r8, rax

	; fork de processo
	mov rax, SYS_fork
	syscall

	; se o retorno do fork for ZERO, significa que está sendo executado
	; a partir do processo filho. Então a rotina "handle" é executada
	test rax, rax
	jz handle

	; quando o retorno não é ZERO, significa que a execução do programa 
	; principal continuou. Então o processo principal volta para o loop
	jmp .accept
</code></pre>
<ul>
<li>
<p>depois de uma chamada ao <strong>fork</strong>, a syscall retorna ZERO quando se está dentro do processo filho. Neste caso, a execução do processo filho continua com a rotina <em>handle</em> e depois termina</p>
</li>
<li>
<p>após a chamada do fork, se o retorno NÃO for ZERO, significa que a execução é do programa principal, então neste caso volta-se ao loop para esperar um novo request no socket</p>
</li>
</ul>
<p>Ao executar com strace, podemos ver várias chamadas à syscall <em>fork</em>:</p>
<pre><code class="language-bash">socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3
bind(3, {sa_family=AF_INET, sin_port=htons(3000), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
listen(3, 2)                            = 0
accept4(3, NULL, NULL, 0)               = 4
fork(strace: Process 12787 attached
)                                  = 12787
[pid 12786] accept4(3, NULL, NULL, 0 &lt;unfinished ...&gt;
[pid 12787] nanosleep({tv_sec=1, tv_nsec=0},  &lt;unfinished ...&gt;
[pid 12786] &lt;... accept4 resumed&gt;)      = 5
[pid 12786] fork(strace: Process 12788 attached
)                      = 12788
[pid 12788] nanosleep({tv_sec=1, tv_nsec=0},  &lt;unfinished ...&gt;
[pid 12786] accept4(3, NULL, NULL, 0)   = 6
[pid 12786] fork(strace: Process 12789 attached
)                      = 12789
[pid 12786] accept4(3, NULL, NULL, 0)   = 7
[pid 12786] fork( &lt;unfinished ...&gt;
[pid 12789] nanosleep({tv_sec=1, tv_nsec=0}, strace: Process 12790 attached
 &lt;unfinished ...&gt;
[pid 12786] &lt;... fork resumed&gt;)         = 12790
[pid 12790] nanosleep({tv_sec=1, tv_nsec=0},  &lt;unfinished ...&gt;
[pid 12786] accept4(3, NULL, NULL, 0)   = 8
[pid 12786] fork(strace: Process 12791 attached
</code></pre>
<p>E os tempos de resposta para 10 requests simultâneos:</p>
<pre><code class="language-bash">$ time echo {1..10} | xargs -n1 -P10 bash -c "time curl localhost:3000"

&lt;h1&gt;Hello, World!&lt;/h1&gt;1.049
&lt;h1&gt;Hello, World!&lt;/h1&gt;&lt;h1&gt;Hello, World!&lt;/h1&gt;&lt;h1&gt;Hello, World!&lt;/h1&gt;&lt;h1&gt;Hello, World!&lt;/h1&gt;1.051
1.053
1.052
1.055
&lt;h1&gt;Hello, World!&lt;/h1&gt;1.051
&lt;h1&gt;Hello, World!&lt;/h1&gt;1.052
&lt;h1&gt;Hello, World!&lt;/h1&gt;1.056
&lt;h1&gt;Hello, World!&lt;/h1&gt;2.106
&lt;h1&gt;Hello, World!&lt;/h1&gt;2.116
2.138
</code></pre>
<p>Yay! Podemos ver que os requests são atendidos de forma concorrente, e que o tempo total ficou em <strong>2,1 segundos</strong> para 10 requests simultâneos!</p>
<blockquote>
<p>Lembrando que, quando estamos lidando com concorrência, não temos controle da ordem de execução dos processos, que são escalonados pelo sistema operacional. Esta preempção de processos pode fazer com que um request que chegou depois seja atendido primeiro. É uma das características de <em>race condition</em> e é por isso que vemos os requests chegando fora de ordem.</p>
</blockquote>
<p>Mas no nosso caso não importa. Cada request é único e não depende do anterior.</p>
<hr />
<h2>Concorrência com clone de processo</h2>
<p>Outra forma muito similar à chamada <em>fork</em> é através da syscall <strong>clone</strong>, que basicamente clona um processo, tal como fizemos no exemplo anterior, garantindo isolamento e concorrência.</p>
<pre><code class="language-as">%define SYS_clone 56
</code></pre>
<p>E a diferença é que chamamos a syscall de clone, ao invés da syscall fork:</p>
<pre><code class="language-as">.accept:
	; int accept(int sockfd, struct *addr, int addrlen, int flags)
	mov rdi, [sockfd]
	mov rsi, 0
	mov rdx, 0
	mov r10, 0
	mov rax, SYS_accept4
	syscall
	mov r8, rax

	; chamada à syscall clone
	; com argumentos a ZERO, significa que será feito um clone do processo
	mov rdi, 0
	mov rsi, 0
	mov rax, SYS_clone
	syscall

	; se o retorno for zero, execução é a partir do processo filho
	test rax, rax
	jz handle

	; continuação da execução do processo principal
	jmp .accept
</code></pre>
<ul>
<li>depois de uma chamada ao <strong>clone</strong>, a syscall retorna ZERO quando se está dentro do processo filho. Neste caso, a execução do processo filho continua com a rotina <em>handle</em> e depois termina</li>
</ul>
<ul>
<li>após a chamada do clone, se o retorno NÃO for ZERO, significa que a execução é do programa principal, então neste caso volta-se ao loop para esperar um novo request no socket</li>
</ul>
<p>Executamos com strace e:</p>
<pre><code class="language-bash">&lt;h1&gt;Hello, World!&lt;/h1&gt;&lt;h1&gt;Hello, World!&lt;/h1&gt;1.062
1.064
&lt;h1&gt;Hello, World!&lt;/h1&gt;&lt;h1&gt;Hello, World!&lt;/h1&gt;1.063
1.061
&lt;h1&gt;Hello, World!&lt;/h1&gt;&lt;h1&gt;Hello, World!&lt;/h1&gt;1.071
1.061
&lt;h1&gt;Hello, World!&lt;/h1&gt;1.059
&lt;h1&gt;Hello, World!&lt;/h1&gt;1.069
&lt;h1&gt;Hello, World!&lt;/h1&gt;&lt;h1&gt;Hello, World!&lt;/h1&gt;2.135
2.128
2.148
</code></pre>
<p>Ainda servindo 10 requests simultâneos <strong>perto dos 2 segundos</strong>! <em>Not bad</em>.</p>
<p>Entretanto, forking ou clone de processos leva a um <strong>gasto excessivo de memória</strong>, pois cada processo filho é exatamente uma cópia do processo principal. Se o principal tem 200MB de memória, com 4 forks teríamos um gasto total de 800MB de memória.</p>
<p>Chegou o momento de falarmos das <strong>threads</strong>.</p>
<hr />
<h2>Concorrência com threads</h2>
<p>Vamos relembrar o que falamos no início do artigo:</p>
<blockquote>
<p>Todo processo tem uma thread chamada <em>thread principal</em>, que é onde está sendo executado o programa</p>
</blockquote>
<p>Apesar de todo programa rodar dentro de uma thread, podemos também criar mais threads que <strong>compartilham a memória do mesmo processo</strong>, e para isto podemos fazer uso da mesma syscall <strong>clone</strong>, mas passando argumentos diferentes que tornam este clone uma <em>thread dentro do mesmo processo</em>, e não uma cópia inteira do processo.</p>
<p>Desta forma, ficamos sempre com UM processo mas atendendo requests em threads diferentes, <strong>gastando assim menos memória</strong> se comparado com forking de processos.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vszydp3du1ntg72xizmd.png" alt="threads" /></p>
<h3>Entendendo a criação de uma thread</h3>
<p>Antes de adaptarmos o código do server para utilizar threads, vamos dar um passo atrás e entender como se cria uma thread em Assembly.</p>
<p>Para criar uma thread, fazemos uso da syscall <strong>clone</strong>. De acordo com a <a href="https://man7.org/linux/man-pages/man2/clone.2.html">documentação</a>, a syscall clone cria um processo "filho", similar ao que fizemos no fork. Mas a diferença é que a syscall clone permite um maior controle sobre o que será compartilhado entre o processo principal e o processo filho.</p>
<pre><code class="language-as">%define SYS_clone 56
</code></pre>
<p>Coisas que podem ser compartilhadas (ou não):</p>
<ul>
<li>
<p>espaço de endereço de memória virtual</p>
</li>
<li>
<p>tabela de descritores de arquivos</p>
</li>
<li>
<p>tabela de handlers de sinais</p>
</li>
<li>
<p>entre outros recursos...</p>
</li>
</ul>
<blockquote>
<p>No exemplo anterior utilizamos a syscall clone passando argumentos a ZERO, o que significa que não queríamos compartilhar nada entre os processos, portanto uma cópia seria feita como no forking de processos</p>
</blockquote>
<p>Para a execução da syscall, precisamos enviar 2 argumentos:</p>
<ul>
<li>
<p><strong>rdi</strong>: representa as <em>flags</em>, que modificam o comportamento do que será compartilhado com o processo filho (thread)</p>
</li>
<li>
<p><strong>rsi</strong>: ponteiro para a função que a thread irá executar, que precisa ser definido dentro de uma <strong>área reservada na memória</strong>, ou seja, precisamos alocar um novo bloco de memória para a thread poder colocar a função e seus argumentos</p>
</li>
</ul>
<p>Portanto, para criar uma thread, precisamos de <strong>thread flags</strong> e <strong>alocação de memória</strong>.</p>
<h3>Thread flags</h3>
<p>Em RDI, vamos passar as seguintes flags:</p>
<ul>
<li>
<p><strong>CLONE_VM</strong>: processo principal e processo filho compartilham o mesmo espaço de memória virtual</p>
</li>
<li>
<p><strong>CLONE_FS</strong>: processos compartilham o mesmo sistema de arquivos</p>
</li>
<li>
<p><strong>CLONE_FILES</strong>: processos compartilham a mesma tabela de descritor de arquivos (file descriptor table)</p>
</li>
<li>
<p><strong>CLONE_SIGHAND</strong>: processos compartilham a mesma tabela de handlers de sinais (signal handlers)</p>
</li>
<li>
<p><strong>CLONE_PARENT</strong>: processos compartilham o mesmo parent, ou seja, o processo "filho" na verdade é filho do processo parent do processo original (mesmo porque estamos falando de uma thread que compartilha o mesmo processo)</p>
</li>
<li>
<p><strong>CLONE_THREAD</strong>: o processo filho é colocado no mesmo grupo de threads do processo original</p>
</li>
<li>
<p><strong>CLONE_IO</strong>: processos compartilham o mesmo contexto de I/O</p>
</li>
</ul>
<blockquote>
<p>No fim das contas, estamos criando um processo "filho" mas que compartilha recursos com o processo principal. <strong>Este é o princípio da thread.</strong></p>
</blockquote>
<pre><code class="language-as">mov rdi, CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_PARENT|CLONE_THREAD|CLONE_IO
</code></pre>
<h3>Alocação de memória com brk</h3>
<p>Em RSI, precisamos indicar o ponteiro para a função na memória, neste caso o ponteiro da rotina <strong>handle</strong>, que tem a lógica de imprimir a mensagem  e etc. Mas aqui não basta indicar o ponteiro, mas sim em <strong>que região da memória</strong> do processo a thread irá armazenar a função, seus argumentos e variáveis locais.</p>
<blockquote>
<p>Cada thread precisa ter sua própria região na memória para armazenar a função e argumentos. É como se a thread tivesse uma área de "stack" só dela</p>
</blockquote>
<p>Para alocar memória, vamos relembrar como funciona o layout de um programa na memória:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/co0lxnlhbx2nz7uhx4k6.png" alt="layout de memória" /></p>
<p>Nos endereços de memória mais baixos, temos o programa, e a seguir temos os <em>dados estáticos</em> (data). No topo, que é onde ficam os endereços mais altos, temos a <em>stack</em>.</p>
<p>E no meio, o que temos <strong>a seguir a seção de dados</strong> é uma área enorme disponível na memória. A syscall <strong>brk</strong> permite mudar o ponto onde <em>termina a seção de dados</em>, também chamado de <strong>program break</strong>.</p>
<p>Podemos ficar mudando este break em direção aos endereços mais altos. Por exemplo, se chamarmos a syscall brk passando argumento ZERO, ela devolve o endereço de memória do program break, que é onde termina a seção de dados:</p>
<pre><code class="language-as">%define SYS_brk 12

...

mov rdi, 0
mov rax, SYS_brk
syscall
</code></pre>
<p>O que temos em RAX é <code>0x403000</code>, que é exatamente o endereço de memória onde termina a seção de dados. Vamos modificar o break <strong>andando UM byte pra frente</strong>:</p>
<pre><code class="language-as">mov rdi, rax
add rdi, 1
mov rax, SYS_brk
syscall
</code></pre>
<p>Agora, RAX traz o endereço do novo program break, que é <code>0x403001</code>. Ou seja, agora podemos manipular este endereço de memória no nosso programa.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d32sk2d6w5eitngvri0y.png" alt="program break" /></p>
<blockquote>
<p>E o quê isto tem a ver com a thread?</p>
</blockquote>
<p>Podemos alocar <strong>uma quantidade arbitrária de bytes</strong> para a thread utilizar nesta área na memória. Como o break é sempre modificado, a próxima thread irá utilizar outra área de memória, e assim sucessivamente!</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bir1ll5slqqmsmhu7q25.png" alt="criação de threads" /></p>
<h3>Um "Hello, world" com threads em Assembly</h3>
<p>Vamos escrever um exemplo simples antes de ir para o web server. Primeiro, definimos as constantes, dentre elas as syscalls e as flags pra criação de threads:</p>
<pre><code class="language-as">global _start

%define SYS_brk 12
%define SYS_clone 56
%define SYS_write 1        
%define SYS_exit 60

%define STDOUT 1
%define CHILD_STACK_SIZE 4096

%define CLONE_VM 0x00000100
%define CLONE_FS 0x00000200
%define CLONE_FILES 0x00000400
%define CLONE_PARENT 0x00008000
%define CLONE_THREAD 0x00010000
%define CLONE_IO 0x80000000
%define CLONE_SIGHAND 0x00000800
</code></pre>
<p>A seguir, na seção de dados, temos a mensagem "Hello, world" que a thread irá imprimir:</p>
<pre><code class="language-as">section .data
msg: db "Hello"
msgLen: equ $ - msg
</code></pre>
<p>Na seção <em>text</em>, o entrypoint do programa faz uma chamada à rotina da <em>thread</em> e a seguir termina:</p>
<pre><code class="language-as">section .text
_start:
	call thread

	mov rdi, 0
	mov rax, SYS_exit
	syscall
</code></pre>
<p>Agora, a definição da rotina <code>handle</code> que a thread irá executar:</p>
<pre><code class="language-as">handle:
	mov rdi, STDOUT
	mov rsi, msg
	mov rdx, msgLen
	mov rax, SYS_write
	syscall

	mov rdi, 0
	mov rax, SYS_exit
	syscall
</code></pre>
<blockquote>
<p>A thread imprime a mensagem no STDOUT e termina. Sim, a thread precisa terminar, caso contrário o sistema emite um segmentation fault</p>
</blockquote>
<p>E por fim, vamos detalhar o processo da rotina da <em>thread</em> (explicação nos comentários do exemplo a seguir):</p>
<pre><code class="language-as">thread:
	; Busca o break atual e guarda em RDX. Na primeira vez, o valor
	; é 0x403000
	mov rdi, 0
	mov rax, SYS_brk
	syscall
	mov rdx, rax

	; Modifica o break atual andando 4096 bytes à frente.
	; Após esta chamada, o break passa a ser 0x404000
	mov rdi, rax
	add rdi, CHILD_STACK_SIZE
	mov rax, SYS_brk
	syscall

	; (1) Thread flags: como deve ser feito o compartilhamento de recursos
	; entre o processo principal e o processo filho
	mov rdi, CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_PARENT|CLONE_THREAD|CLONE_IO
	
	; (2a) Endereço de memória em RSI: é o break atual + 4096 bytes.
	; Retiramos também 8 bytes para caber o ponteiro da função
	lea rsi, [rdx + CHILD_STACK_SIZE - 8]

	; (2b) No endereço em RSI colocamos o ponteiro da função "handle".
	; Como endereçamento em x86_64 é de 8 bytes, é por isto
	; que no passo anterior fizemos [rdx + 4096 - 8]
	mov qword [rsi], handle
	mov rax, SYS_clone
	syscall
	ret
</code></pre>
<p>E pronto, ao executar o programa, temos a mensagem "Hello" na saída do programa que foi feita pela thread.</p>
<p>Caso o programa principal faça <code>call thread</code> 2 vezes, a próxima thread irá ter em RSI o break modificado, iniciando em <code>0x404000</code>.</p>
<h3>Modificando o server para suportar multi-threading</h3>
<p>Agora vamos trazer o código necessário para modificar o server:</p>
<pre><code class="language-as">%define SYS_clone 56
%define SYS_brk 12
</code></pre>
<p>Thread flags:</p>
<pre><code class="language-as">%define CHILD_STACK_SIZE 4096
%define CLONE_VM 0x00000100
%define CLONE_FS 0x00000200
%define CLONE_FILES 0x00000400
%define CLONE_PARENT 0x00008000
%define CLONE_THREAD 0x00010000
%define CLONE_IO 0x80000000
%define CLONE_SIGHAND 0x00000800
</code></pre>
<p>Rotina <em>accept</em>:</p>
<pre><code class="language-as">.accept:
	; int accept(int sockfd, struct *addr, int addrlen, int flags)
	mov rdi, [sockfd]
	mov rsi, 0
	mov rdx, 0
	mov r10, 0
	mov rax, SYS_accept4
	syscall
	mov r8, rax

	; Chamada da thread. Irá ser executada assincronamente tal como no
	; forking de processos, mas compartilhando a memória do
	; processo principal
	call thread

	; Processo principal volta para o loop
	jmp .accept
</code></pre>
<p>Definição da <em>thread</em>:</p>
<pre><code class="language-as">thread:
	mov rdi, 0
	mov rax, SYS_brk
	syscall
	mov rdx, rax

	mov rdi, rax
	add rdi, CHILD_STACK_SIZE
	mov rax, SYS_brk
	syscall

	mov rdi, CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_PARENT|CLONE_THREAD|CLONE_IO
	lea rsi, [rdx + CHILD_STACK_SIZE - 8]
	mov qword [rsi], handle
	mov rax, SYS_clone
	syscall
	ret
</code></pre>
<blockquote>
<p>Aqui seguimos o mesmo padrão do exemplo anterior: aloca memória com brk e a seguir executa a syscall clone com as flags de compartilhamento de recursos</p>
</blockquote>
<p>E a lógica da rotina <em>handle</em> <strong>que será executada pela thread</strong>, que faz o <em>sleep</em>, a seguir escreve no socket a mensagem, fecha o socket da requisição e por fim termina sua execução:</p>
<pre><code class="language-as">handle:
	lea rdi, [timespec]
	mov rax, SYS_nanosleep
	syscall

	; int write(fd)
	mov rdi, r8
	mov rsi, response
	mov rdx, responseLen
	mov rax, SYS_write
	syscall

	; int close(fd)
	mov rdi, r8
	mov rax, SYS_close
	syscall

	mov rdi, 0
	mov rax, SYS_exit
	syscall
</code></pre>
<p>Com 1 chamada isolada, temos o seguinte output com strace:</p>
<pre><code class="language-bash">socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3
bind(3, {sa_family=AF_INET, sin_port=htons(3000), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
listen(3, 2)                            = 0
accept4(3, NULL, NULL, 0)               = 4
brk(NULL)                               = 0x9da000
brk(0x9db000)                           = 0x9db000
clone(child_stack=0x9daff8, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_PARENT|CLONE_THREAD|CLONE_IOstrace: Process 13078 attached
) = 13078
[pid 13078] nanosleep({tv_sec=1, tv_nsec=0},  &lt;unfinished ...&gt;
[pid 13077] accept4(3, NULL, NULL, 0 &lt;unfinished ...&gt;
[pid 13078] &lt;... nanosleep resumed&gt;0x9daff8) = 0
[pid 13078] write(4, "HTTP/1.1 200 OK\r\nContent-Type: t"..., 86) = 86
[pid 13078] close(4)                    = 0
[pid 13078] exit(0)                     = ?
[pid 13078] +++ exited with 0 +++
&lt;... accept4 resumed&gt;)                  = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGWINCH {si_signo=SIGWINCH, si_code=SI_KERNEL} ---
accept4(3, NULL, NULL, 0
</code></pre>
<p>Vamos reparar na sequência de chamadas:</p>
<ul>
<li>
<p>depois do accept, foi feita uma chamada a <strong>brk</strong> modificando o program break (alocando memória)</p>
</li>
<li>
<p>a seguir, vemos a chamada <strong>clone</strong>, que acoplou o processo filho 13078</p>
</li>
<li>
<p>a thread (13078) é suspensa com <strong>nanosleep</strong> por 1 segundo</p>
</li>
<li>
<p>o processo principal (13077) volta para o <strong>accept</strong> e fica a espera de mais requests</p>
</li>
<li>
<p>a thread <strong>escreve</strong> no socket</p>
</li>
<li>
<p>a thread <strong>fecha</strong> o socket</p>
</li>
<li>
<p>a thread é encerrada com <strong>exit</strong></p>
</li>
</ul>
<p>Agora, simulando os 10 requests simultâneos:</p>
<pre><code class="language-bash">&lt;h1&gt;Hello, World!&lt;/h1&gt;1.060
&lt;h1&gt;Hello, World!&lt;/h1&gt;1.064
&lt;h1&gt;Hello, World!&lt;/h1&gt;1.064
&lt;h1&gt;Hello, World!&lt;/h1&gt;1.062
&lt;h1&gt;Hello, World!&lt;/h1&gt;1.068
&lt;h1&gt;Hello, World!&lt;/h1&gt;1.073
&lt;h1&gt;Hello, World!&lt;/h1&gt;&lt;h1&gt;Hello, World!&lt;/h1&gt;&lt;h1&gt;Hello, World!&lt;/h1&gt;&lt;h1&gt;Hello, World!&lt;/h1&gt;2.098
2.094
2.097
2.091
2.113
</code></pre>
<p><em>Superb!</em> Temos o mesmo tempo total de <strong>2,1 segundos</strong> mas gastando <strong>muito menos memória</strong>!</p>
<p>Entretanto, temos um pequeno problema. Imagina que num momento de grande número de acessos, a nossa aplicação recebe 1000 requests concorrentes. E se receber 5000? Ou então <strong>dezenas de milhares</strong> de requests simultâneos?</p>
<p>Uma <strong>chamada de sistema tem custo</strong>. O sistema operacional oferece um limite de threads que podem ser criadas ao mesmo tempo por processo. Se deixarmos assim, nossa aplicação corre um grande risco de ultrapassar esse limite, além de que chamadas a <em>brk + clone</em> têm seus custos de criação.</p>
<p>E se pudéssemos reciclar um número limitado de threads? Sim, estamos falando de <strong>pool de threads</strong>.</p>
<hr />
<h2>Concorrência com thread pool</h2>
<p>A forma mais comum de trabalhar com thread é com <em>thread pool</em>. Basicamente, definimos um número arbitrário de threads <strong>que nunca terminam</strong>, mas ficam em loop consumindo mensagens de alguma estrutura de dados. Esta estrutura pode ser uma <strong>fila</strong>.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i8ysvzs13lo7jju7opfs.png" alt="thread pool" /></p>
<h3>Uma thread em loop</h3>
<p>Vamos inicialmente definir que teremos apenas UMA thread em loop lendo mensagens da fila. O processo deverá ser o seguinte:</p>
<ul>
<li>
<p>processo principal inicia uma thread</p>
</li>
<li>
<p>a thread fica em loop lendo mensagens (socket) da fila. Quando tiver vazia, repete o loop. Quando houver algum socket na fila, a thread executa a lógica que é fazer o <em>nanosleep</em>, escrever no socket, fechar o socket, e voltar para o loop de leitura da fila</p>
</li>
<li>
<p>processo principal continua execução após criação da thread, onde fica em loop lendo requisições que chegam no socket (accept). Quando uma requisição chega, adiciona o socket na fila e volta para o loop do <strong>accept</strong>.</p>
</li>
</ul>
<p>Vamos passo a passo, começando pelas constantes:</p>
<pre><code class="language-as">global _start

%define SYS_socket 41
%define SYS_bind 49
%define SYS_listen 50
%define SYS_accept4 288
%define SYS_write 1
%define SYS_close 3

%define SYS_nanosleep 35
%define SYS_clone 56
%define SYS_brk 12
%define SYS_exit 60

%define AF_INET 2
%define SOCK_STREAM 1
%define SOCK_PROTOCOL 0
%define BACKLOG 2
%define CR 0xD
%define LF 0xA

%define CHILD_STACK_SIZE 4096
%define CLONE_VM 0x00000100
%define CLONE_FS 0x00000200
%define CLONE_FILES 0x00000400
%define CLONE_PARENT 0x00008000
%define CLONE_THREAD 0x00010000
%define CLONE_IO 0x80000000
%define CLONE_SIGHAND 0x00000800
</code></pre>
<p>A seguir, a seção de dados:</p>
<pre><code class="language-as">section .data
sockaddr:
	sa_family: dw AF_INET   ; 2 bytes
	port: dw 0xB80B         ; 2 bytes
	ip_addr: dd 0           ; 4 bytes
	sin_zero: dq 0          ; 8 bytes
response: 
	headline: db "HTTP/1.1 200 OK", CR, LF
	content_type: db "Content-Type: text/html", CR, LF
	content_length: db "Content-Length: 22", CR, LF
	crlf: db CR, LF
	body: db "&lt;h1&gt;Hello, World!&lt;/h1&gt;"
responseLen: equ $ - response
timespec:
	tv_sec: dq 1
	tv_nsec: dq 0
queuePtr: db 0

section .bss
sockfd: resb 8
queue: resb 8
</code></pre>
<p>Repare que a fila representa 8 bytes fixos (para nosso exemplo é o suficiente), utilizando também um ponteiro para manipular a fila.</p>
<p>Seguindo com o código, o programa inicia logo disparando a thread:</p>
<pre><code class="language-as">section .text
_start:
	call thread        
</code></pre>
<p>A seguir vêm as rotinas habituais (vou omitir pra poupar caracteres neste artigo): <em>socket, bind e listen</em>.</p>
<p>O <strong>accept</strong> fica da seguinte forma:</p>
<pre><code class="language-as">.accept:
	; int accept(int sockfd, struct *addr, int addrlen, int flags)
	mov rdi, [sockfd]
	mov rsi, 0
	mov rdx, 0
	mov r10, 0
	mov rax, SYS_accept4
	syscall

	mov r8, rax
	call enqueue

	jmp .accept
</code></pre>
<p>Okay, agora, ao invés de <em>chamar uma thread</em>, o programa principal enfileira o socket da requisição. Lógica do <code>enqueue</code>:</p>
<pre><code class="language-as">enqueue:
	xor rdx, rdx
	mov dl, [queuePtr]	
	mov [queue + rdx], r8	
	inc byte [queuePtr]
	ret
</code></pre>
<p>Aqui, estamos manipulando o ponteiro em <code>queue</code> utilizando <code>queuePtr</code>, incrementando um byte quando algo é adicionado na fila.</p>
<p>Agora vamos à implementação da rotina da thread:</p>
<pre><code class="language-as">thread:
	mov rdi, 0
	mov rax, SYS_brk
	syscall
	mov rdx, rax

	mov rdi, rax
	add rdi, CHILD_STACK_SIZE
	mov rax, SYS_brk
	syscall

	mov rdi, CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_PARENT|CLONE_THREAD|CLONE_IO
	lea rsi, [rdx + CHILD_STACK_SIZE - 8]
	mov qword [rsi], handle
	mov rax, SYS_clone
	syscall
	ret
</code></pre>
<p>Nada de novo por enquanto. O que modifica é a rotina <em>handle</em> (explicação detalhada nos comentários):</p>
<pre><code class="language-as">handle:
	; Verifica se a fila está vazia. Se estiver, fica em loop infinito.
	; Repare que este código não está otimizado. Loop infinito 
	; acarreta alto consumo de CPU. Nas próximas seções vamos resolver isto.
	; Por ora, vamos aceitar este consumo de CPU.
	cmp byte [queuePtr], 0
	je handle

	; Remove (faz pop) da fila de socket, e guarda em R8.
	call dequeue
	mov r8, rax

	; Processo normal, faz o nanosleep de 1 segundo simulando latência
	lea rdi, [timespec]
	mov rax, SYS_nanosleep
	syscall

	; Escreve no socket que está em R8
	; int write(fd)
	mov rdi, r8
	mov rsi, response
	mov rdx, responseLen
	mov rax, SYS_write
	syscall

	; Fecha o socket
	; int close(fd)
	mov rdi, r8
	mov rax, SYS_close
	syscall

	; Volta para o início (loop)
	jmp handle
</code></pre>
<p>E por último, a lógica da rotina <em>dequeue</em>:</p>
<pre><code class="language-as">dequeue:
	xor rax, rax
	xor rsi, rsi

	mov al, [queue]
	mov rcx, 0
.loop_dequeue:
	cmp byte [queuePtr], 0
	je .return_dequeue

	cmp cl, [queuePtr]
	je .done_dequeue

	; shift
	xor r10, r10
	mov r10b, [queue + rcx + 1]
	mov byte [queue + rcx], r10b

	inc rcx
	jmp .loop_dequeue
.done_dequeue:
	dec byte [queuePtr]
.return_dequeue:
	ret
</code></pre>
<blockquote>
<p>Por enquanto não vou entrar em detalhes em como trabalhar com filas em Assembly. Vou deixar estes detalhes para outro artigo, que irá tratar especificamente de arrays, filas e listas ligadas. Em breve!</p>
</blockquote>
<p>Pronto, já podemos executar o server e...</p>
<pre><code class="language-bash">&lt;h1&gt;Hello, World!&lt;/h1&gt;1.042
&lt;h1&gt;Hello, World!&lt;/h1&gt;2.059
&lt;h1&gt;Hello, World!&lt;/h1&gt;3.071
&lt;h1&gt;Hello, World!&lt;/h1&gt;4.083
&lt;h1&gt;Hello, World!&lt;/h1&gt;5.090
&lt;h1&gt;Hello, World!&lt;/h1&gt;6.094
&lt;h1&gt;Hello, World!&lt;/h1&gt;7.112
&lt;h1&gt;Hello, World!&lt;/h1&gt;8.121
&lt;h1&gt;Hello, World!&lt;/h1&gt;9.140
&lt;h1&gt;Hello, World!&lt;/h1&gt;10.150
10.166
</code></pre>
<p><em>Ouch!</em> Voltamos aos 10 segundos. Mas isto se deve ao fato de termos apenas <em>uma thread</em> em loop. Vamos aumentar o número de threads na pool.</p>
<h3>5 threads em loop</h3>
<p>A seguir modificamos o programa para inicializar 5 threads, para que desta forma nosso server tenha mais capacidade em atender requests simultâneos:</p>
<pre><code class="language-as">section .text
_start:
.initialize_pool:
	mov r8, 0
.pool:
	call thread        
	inc r8
	cmp r8, 5
	je .socket
	jmp .pool
....
....
</code></pre>
<p>Com este loop fazemos <code>call thread</code> 5 vezes, pelo que cada thread, e ainda utilizando o exemplo anterior, irá ficar em loop buscando mensagens na fila.</p>
<p>Executamos o código com 1 request e temos sucesso:</p>
<pre><code class="language-bash">$ time curl localhost:3000

&lt;h1&gt;Hello, World!&lt;/h1&gt;1.022
</code></pre>
<p>Mas no output do strace, após a resposta, vemos uma sequência de erros das threads:</p>
<pre><code class="language-bash">&lt;h1&gt;Hello, World!&lt;/h1&gt;[pid 13483] write(4, "HTTP/1.1 200 OK\r\nContent-Type: t"..., 86 &lt;unfinished ...&gt;
[pid 13482] write(0, "HTTP/1.1 200 OK\r\nContent-Type: t"..., 86 &lt;unfinished ...&gt;
[pid 13480] write(0, "HTTP/1.1 200 OK\r\nContent-Type: t"..., 86 &lt;unfinished ...&gt;
[pid 13484] &lt;... write resumed&gt;)        = 86
[pid 13481] write(4, "HTTP/1.1 200 OK\r\nContent-Type: t"..., 86HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 22

&lt;h1&gt;Hello, World!&lt;/h1&gt; &lt;unfinished ...&gt;
[pid 13480] &lt;... write resumed&gt;)        = 86
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 22

&lt;h1&gt;Hello, World!&lt;/h1&gt;[pid 13484] close(0 &lt;unfinished ...&gt;
[pid 13483] &lt;... write resumed&gt;)        = 86
[pid 13482] &lt;... write resumed&gt;)        = 86
[pid 13480] close(0 &lt;unfinished ...&gt;
[pid 13484] &lt;... close resumed&gt;)        = 0
[pid 13481] &lt;... write resumed&gt;)        = 86
[pid 13484] nanosleep({tv_sec=1, tv_nsec=0},  &lt;unfinished ...&gt;
[pid 13483] close(4 &lt;unfinished ...&gt;
[pid 13482] close(0 &lt;unfinished ...&gt;
[pid 13481] close(4 &lt;unfinished ...&gt;
[pid 13480] &lt;... close resumed&gt;)        = -1 EBADF (Bad file descriptor)
[pid 13483] &lt;... close resumed&gt;)        = 0
[pid 13482] &lt;... close resumed&gt;)        = -1 EBADF (Bad file descriptor)
[pid 13481] &lt;... close resumed&gt;)        = -1 EBADF (Bad file descriptor)
</code></pre>
<p>A thread tentou fechar o socket descriptor da requisição mas outra thread tentou ler o socket de forma concorrente (Bad file descriptor).</p>
<p>Quando envolve <strong>mais de uma thread</strong> consumindo o mesmo recurso (neste caso a fila), precisamos de um mecanismo de <strong>sincronização</strong>, que no caso são <em>locks</em>.</p>
<h3>Sincronização com futex</h3>
<p>Com <em>locks</em>, conseguimos controlar o acesso a um recurso compartilhado <em>entre diferentes threads</em>.</p>
<p>Através da syscall <strong>futex</strong>, podemos suspender uma thread baseando-se em uma "variável condicional". De forma oposta, podemos <em>tornar uma thread de volta à execução</em> baseando-se também na variável condicional.</p>
<p>Esta técnica de <em>variável condicional</em> (condvar) é um primitivo de sincronização bastante utilizado. No nosso caso para controle da fila, queremos o seguinte cenário:</p>
<ul>
<li>
<p>a thread verifica se há algo na fila. Caso a fila esteja vazia, a thread é suspensa com <em>futex wait</em> através de uma variável condicional</p>
</li>
<li>
<p>quando algo for adicionado na fila, outra thread/processo "emite um sinal" chamando a syscall <em>futex wake</em> na mesma variável condicional.</p>
</li>
<li>
<p>quando o sinal é emitido, neste momento a thread que tem o acesso ao lock (variável condicional) é trazida de volta ao contexto, então lê a mensagem da fila e executa a ação necessária. Após, se a fila estiver vazia, repete o processo com <em>futex wait</em> e fica novamente suspensa</p>
</li>
</ul>
<blockquote>
<p>Desta forma, garantimos que as threads não ficam consumindo a CPU em loop indefinidamente</p>
</blockquote>
<p>Modificando o código, começamos por definir a syscall:</p>
<pre><code class="language-as">%define SYS_futex 202
</code></pre>
<p>A seguir, na seção de dados <code>.bss</code> declaramos a <em>variável condicional</em> ocupando 8 bytes, que será utilizada como sincronização do futex:</p>
<pre><code class="language-as">section .bss
...
condvar: resb 8
</code></pre>
<p>Na rotina <code>enqueue</code>, <em>emitimos o sinal</em> após o socket ser adicionado na fila:</p>
<pre><code class="language-as">enqueue:
	xor rdx, rdx
	mov dl, [queuePtr]	
	mov [queue + rdx], r8	
	inc byte [queuePtr]

	call emit_signal
	ret
</code></pre>
<p>A lógica o <em>emit_signal</em> (explicação nos comentários):</p>
<pre><code class="language-as">emit_signal:
   ; Endereço de memória para a variável condicional (8 bytes)
   mov rdi, condvar
   
   ; Flags do Futex (WAKE), que irá trazer a thread
   ; de volta ao contexto
   mov rsi, FUTEX_WAKE | FUTEX_PRIVATE_FLAG 

   ; Argumentos adicionais, que neste caso vamos deixar a ZERO
   xor rdx, rdx
   xor r10, r10
   xor r8, r8

   ; Chamada da syscall
   mov rax, SYS_futex
   syscall
   ret
</code></pre>
<p>Agora, modificamos a rotina <em>handle</em>:</p>
<pre><code class="language-as">handle:	
	; Caso a fila esteja vazia, fazemos jump para "wait"
	cmp byte [queuePtr], 0         
	je .wait           

	; Faz pop do socket da fila e segue o fluxo normal
	call dequeue      
	mov r10, rax

	lea rdi, [timespec]
	mov rax, SYS_nanosleep
	syscall

	; int write(fd)
	mov rdi, r10
	mov rsi, response
	mov rdx, responseLen
	mov rax, SYS_write
	syscall

	; int close(fd)
	mov rdi, r10
	mov rax, SYS_close
	syscall

	; Volta para o início
	jmp handle       
.wait:
	; Chamada para wait_condvar, que vai suspender a thread atual com FUTEX
	call wait_condvar 
	jmp handle       
</code></pre>
<p>E, por último e não menos importante, a lógica da rotina <em>wait_condvar</em>, que suspende a thread de execução:</p>
<pre><code class="language-as">wait_condvar:
   ; Endereço de memória para a variável condicional (8 bytes)
   mov rdi, condvar   

   ; Flags do Futex (WAIT), que irá suspender a thread
   mov rsi, FUTEX_WAIT | FUTEX_PRIVATE_FLAG 
   xor rdx, rdx
   xor r10, r10              
   xor r8, r8               
   mov rax, SYS_futex
   syscall
   test rax, rax
   jz .done_condvar
.done_condvar:
   ret
</code></pre>
<p>Assim que iniciamos o server com <em>strace</em>, podemos ver as syscalls em ação:</p>
<pre><code class="language-bash">brk(NULL)                               = 0x155c000
brk(0x155d000)                          = 0x155d000
clone(child_stack=0x155cff8, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_PARENT|CLONE_THREAD|CLONE_IOstrace: Process 13539 attached
) = 13539
[pid 13539] futex(0x402088, FUTEX_WAIT_PRIVATE, 0, NULL &lt;unfinished ...&gt;
[pid 13538] brk(NULL)                   = 0x155d000
[pid 13538] brk(0x155e000)              = 0x155e000
[pid 13538] clone(child_stack=0x155dff8, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_PARENT|CLONE_THREAD|CLONE_IOstrace: Process 13540 attached
) = 13540
[pid 13540] futex(0x402088, FUTEX_WAIT_PRIVATE, 0, NULL &lt;unfinished ...&gt;
[pid 13538] brk(NULL)                   = 0x155e000
[pid 13538] brk(0x155f000)              = 0x155f000
[pid 13538] clone(child_stack=0x155eff8, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_PARENT|CLONE_THREAD|CLONE_IOstrace: Process 13541 attached
) = 13541
[pid 13541] futex(0x402088, FUTEX_WAIT_PRIVATE, 0, NULL &lt;unfinished ...&gt;
[pid 13538] brk(NULL)                   = 0x155f000
[pid 13538] brk(0x1560000)              = 0x1560000
[pid 13538] clone(child_stack=0x155fff8, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_PARENT|CLONE_THREAD|CLONE_IOstrace: Process 13542 attached
) = 13542
[pid 13542] futex(0x402088, FUTEX_WAIT_PRIVATE, 0, NULL &lt;unfinished ...&gt;
[pid 13538] brk(NULL)                   = 0x1560000
[pid 13538] brk(0x1561000)              = 0x1561000
[pid 13538] clone(child_stack=0x1560ff8, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_PARENT|CLONE_THREAD|CLONE_IOstrace: Process 13543 attached
) = 13543
[pid 13538] socket(AF_INET, SOCK_STREAM, IPPROTO_IP &lt;unfinished ...&gt;
[pid 13543] futex(0x402088, FUTEX_WAIT_PRIVATE, 0, NULL &lt;unfinished ...&gt;
[pid 13538] &lt;... socket resumed&gt;)       = 3
[pid 13538] bind(3, {sa_family=AF_INET, sin_port=htons(3000), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
[pid 13538] listen(3, 2)                = 0
[pid 13538] accept4(3, NULL, NULL, 0)   = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
[pid 13538] --- SIGWINCH {si_signo=SIGWINCH, si_code=SI_KERNEL} ---
[pid 13538] accept4(3, NULL, NULL, 0
</code></pre>
<p>Repare que cada thread faz a chamada a futex com <em>FUTEX WAIT</em>, e isto vemos acontecer 5 vezes no trace. Ou seja, as 5 threads estão suspensas <strong>sem consumir CPU</strong>.</p>
<p>Ao fazer o primeiro request, temos o seguinte resultado:</p>
<pre><code class="language-bash">)   = 4
[pid 13538] futex(0x402088, FUTEX_WAKE_PRIVATE, 0) = 1
[pid 13539] &lt;... futex resumed&gt;)        = 0
[pid 13538] accept4(3, NULL, NULL, 0 &lt;unfinished ...&gt;
[pid 13539] nanosleep({tv_sec=1, tv_nsec=0}, NULL) = 0
[pid 13539] write(4, "HTTP/1.1 200 OK\r\nContent-Type: t"..., 86) = 86
[pid 13539] close(4)                    = 0
[pid 13539] futex(0x402088, FUTEX_WAIT_PRIVATE, 0, NULL
</code></pre>
<ul>
<li>
<p>o processo principal recebeu a mensagem no socket, enfileirou e executou <em>futex</em> com <strong>FUTEX_WAKE</strong></p>
</li>
<li>
<p>uma das threads foi trazida de volta ao contexto e fez a sua devida execução (nanosleep + write + close)</p>
</li>
<li>
<p>o processo principal voltou para o accept a espera de mais requests no socket</p>
</li>
<li>
<p>a thread terminou seu trabalho, viu que não tinha mais nada na fila e executou <em>futex</em> com <strong>FUTEX_WAIT</strong>, ficando novamente suspensa</p>
</li>
</ul>
<p>Finalmente, podemos executar 10 requests simultâneos e...</p>
<pre><code class="language-bash">&lt;h1&gt;Hello, World!&lt;/h1&gt;&lt;h1&gt;Hello, World!&lt;/h1&gt;&lt;h1&gt;Hello, World!&lt;/h1&gt;1.079
1.082
1.083
&lt;h1&gt;Hello, World!&lt;/h1&gt;&lt;h1&gt;Hello, World!&lt;/h1&gt;1.062
1.083
&lt;h1&gt;Hello, World!&lt;/h1&gt;&lt;h1&gt;Hello, World!&lt;/h1&gt;2.087
2.088
&lt;h1&gt;Hello, World!&lt;/h1&gt;2.100
&lt;h1&gt;Hello, World!&lt;/h1&gt;2.101
&lt;h1&gt;Hello, World!&lt;/h1&gt;2.110
2.127
</code></pre>
<p><em>Nice!</em> Com uma pool de 5 threads, conseguimos atingir <strong>2,1 segundos</strong> para 10 requests concorrentes. Agora temos concorrência consumindo muito menos recursos:</p>
<ul>
<li>
<p>menos memória, pois não é forking de processos</p>
</li>
<li>
<p>menos CPU, pois as threads não ficam em loop infinito</p>
</li>
<li>
<p>menos latência, pois com uma limitação de 5 threads, novos requests não criam novas threads</p>
</li>
</ul>
<hr />
<h2>Alocação de memória com mmap</h2>
<p>Um problema comum ao utilizar <em>brk</em> é que a memória pode ficar fragmentada. Uma vez que o program break foi modificado, aquela memória pode ser utilizada, mas torna muito difícil ser reciclada.</p>
<p>Uma forma de lidar com este problema de fragmentação é utilizar uma syscall que trata de reservar uma área na memória que pode ser reciclada futuramente. Estamos falando da syscall <strong>mmap</strong>.</p>
<pre><code class="language-as">thread:
	mov rdi, 0x0
	mov rsi, CHILD_STACK_SIZE
	mov rdx, PROT_WRITE | PROT_READ
	mov r10, MAP_ANONYMOUS | MAP_PRIVATE | MAP_GROWSDOWN
	mov rax, SYS_mmap
	syscall

	mov rdi, CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_PARENT|CLONE_THREAD|CLONE_IO
	lea rsi, [rax + CHILD_STACK_SIZE - 8]
	mov qword [rsi], handle
	mov rax, SYS_clone
	syscall
	ret
</code></pre>
<p>Ao invés de chamar <em>brk</em>, podemos chamar <strong>mmap</strong>, especificando:</p>
<ul>
<li>
<p><strong>rdi</strong>: endereço de memória onde deve ser mapeado. Se tiver ZERO, o sistema operacional se encarrega de trazer um endereço de memória disponível</p>
</li>
<li>
<p><strong>rsi</strong>: tamanho do espaço reservado na memória. No nosso caso, queremos 4096 bytes para a thread</p>
</li>
<li>
<p><strong>rdx</strong>: proteção de memória, neste caso queremos que a memória possa ser tanto escrita (PROT_WRITE) quanto lida (PROT_READ) pela thread</p>
</li>
<li>
<p><strong>r10</strong>: flags de mapeamento</p>
<ul>
<li><strong>MAP_ANONYMOUS</strong>: o mapeamento não é associado a nenhum arquivo ou descritor de arquivo (modo anônimo)</li>
<li><strong>MAP_PRIVATE</strong>: mapeamento privado com <em>copy-on-write</em>, ou seja, os dados serão copiados à medida em que são escritos</li>
<li><strong>MAP_GROWSDOWN</strong>: o mapeamento é usado no formato "stack", ou seja, o mapeamento é dos endereços maiores em direção aos menores</li>
</ul>
</li>
</ul>
<p>Com <em>mmap</em>, podemos fazer uso da sua contrapartida, a syscall <strong>unmmap</strong>, que permite reciclar uma determinada área na memória que não é mais utilizada, evitando fragmentação.</p>
<blockquote>
<p>Esta técnica é muito utilizada pela libc através da função <strong>malloc</strong>. Com mmap podemos definir uma memória heap para nosso programa</p>
</blockquote>
<p>É isto, na prática não terá nenhum efeito com relação ao exemplo anterior. Mas esta seção foi apenas para trazer uma forma diferente de alocar memória para a thread.</p>
<hr />
<h2>Conclusão</h2>
<p>Ufa! Finalmente chegamos ao final da saga. Passamos por uma <a href="https://leandronsp.com/articles/construindo-um-web-server-em-assembly-x86-parte-i-introducao-14p5">introdução</a>, onde a seguir fizemos uma abordagem pela <a href="https://leandronsp.com/articles/construindo-um-web-server-em-assembly-x86-parte-ii-historia-e-arquitetura-2jb9">história e arquitetura de computadores</a>, para então analisar <a href="https://leandronsp.com/articles/construindo-um-web-server-em-assembly-x86-parte-iii-codigo-de-maquina-bgk">código de máquina</a>, que foi a base para entrar em <a href="https://leandronsp.com/articles/construindo-um-web-server-em-assembly-x86-parte-iv-um-assembly-modesto-oif">assembly x86</a> de fato, para finalmente <a href="https://leandronsp.com/articles/construindo-um-web-server-em-assembly-x86-parte-v-finalmente-o-server-9e5">concluir o desenvolvimento do web server</a>.</p>
<p>Este último artigo foi uma abordagem para multi-threading em Assembly, passando por conceitos de concorrência como <em>forking de processos</em>, <strong>clone</strong>, threading, pool de threads e sincronização com locks.</p>
<p>Declaro aqui então o fim da <strong>saga desenvolvendo um web server em Assembly x86</strong>.</p>
<p><em>Até a próxima saga!</em></p>
<hr />
<h2>Referências</h2>
<sub>
Synchronization: mutexes and condition variables
https://cs61.seas.harvard.edu/site/2018/Synch3/
Synchronization, atomics and mutexes
https://cs.brown.edu/courses/csci0300/2023/notes/l22.html
Basics of Futexes
https://eli.thegreenplace.net/2018/basics-of-futexes/
Raw Linux threads via syscalls
https://nullprogram.com/blog/2015/05/15/
Condition variables with Futex
https://www.remlab.net/op/futex-condvar.shtml
</sub>
]]></description>
<pubDate>2024-07-14</pubDate>
</item>
<item>
<title>Construindo um web server em Assembly x86, parte V, finalmente o server</title>
<link>https://leandronsp.com/articles/construindo-um-web-server-em-assembly-x86-parte-v-finalmente-o-server-9e5.html</link>
<guid>https://leandronsp.com/articles/construindo-um-web-server-em-assembly-x86-parte-v-finalmente-o-server-9e5.html</guid>
<description><![CDATA[<p>No <a href="https://leandronsp.com/articles/construindo-um-web-server-em-assembly-x86-parte-iv-um-assembly-modesto-oif">artigo anterior</a>, passamos pelos fundamentos de Assembly, onde foi possível entender alguns conceitos básicos tais como tipos de registradores, <strong>stack</strong>, loops, FLAGS etc, tudo sendo feito com debugging via <em>GDB</em>.</p>
<p>Agora, vamos de fato construir um web server muito simples que devolve um HTML com a frase "Hello, World". A meta é chegarmos nisto:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6745f6h98cph2u50aa8x.png" alt="hello world" /></p>
<p>O processo para chegarmos a este objetivo consiste em cobrir fundamentos de Web, passando por sockets, TCP e HTTP, enquanto vamos explorando conceitos práticos em Assembly x86.</p>
<hr />
<h2>Agenda</h2>
<ul>
<li>
<p><a href="#arquitetura-web">Arquitetura Web</a></p>
<ul>
<li><a href="#clienteservidor">Cliente-servidor</a></li>
<li><a href="#modelo-osi">Modelo OSI</a></li>
<li><a href="#sockets-e-tcp">Sockets e TCP</a></li>
<li><a href="#http">HTTP</a></li>
</ul>
</li>
<li>
<p><a href="#como-funciona-um-servidor-web">Como funciona um servidor web</a></p>
<ul>
<li><a href="#4-syscalls-para-o-resgate">4 syscalls para o resgate</a></li>
</ul>
</li>
<li>
<p><a href="#um-server-modesto-em-assembly">Um server modesto em Assembly</a></p>
<ul>
<li><a href="#criando-o-socket">Criando o socket</a></li>
<li><a href="#fazendo-bind-no-socket">Fazendo bind no socket</a></li>
<li><a href="#preparando-para-receber-conex%C3%B5es">Preparando para receber conexões</a></li>
<li><a href="#chegou-o-momento-de-aceitar-clientes">Chegou o momento de aceitar clientes</a></li>
<li><a href="#resposta-do-servidor-e-fechamento-da-conex%C3%A3o">Resposta do servidor e fechamento da conexão</a></li>
<li><a href="#mas-o-servidor-deve-ficar-em-loop-n%C3%A3o">Mas o servidor deve ficar em loop, não?</a></li>
</ul>
</li>
<li>
<p><a href="#conclus%C3%A3o">Conclusão</a></p>
</li>
<li>
<p><a href="#refer%C3%AAncias">Referências</a></p>
</li>
</ul>
<hr />
<h2>Arquitetura Web</h2>
<p>Para criar um servidor web, precisamos manipular <em>mensagens HTTP</em>, que são transportadas via camada de transporte TCP/IP através de uma rede.</p>
<p>Estas mensagens são enviadas entre diferentes dispositivos conectados a uma rede, que pode ser privada (local) ou pública. Regularmente, comunicação HTTP é feita entre 2 dispositivos, sendo um deles o <em>cliente</em> e outro o <em>servidor</em>.</p>
<p>Vamos brevemente falar de cada um destes conceitos.</p>
<h3>Cliente-servidor</h3>
<p>Numa arquitetura cliente-servidor, temos 2 dispositivos conectados a uma rede de computadores:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z9io56i8xzadm9cqvepe.png" alt="cliente servidor" /></p>
<p>Para um servidor web, é necessário que o cliente realize uma <strong>conexão</strong> com o servidor, em seguida faça uma <strong>requisição</strong>, pelo que o servidor deve devolver uma <strong>resposta</strong> e, por último, <strong>fechar a conexão</strong>.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/maxau6kx9t18u6r049r0.png" alt="connect request response" /></p>
<p>Mas como esta mensagem deve ser enviada? Quem garante a entrega? E caso ocorra falha de sinal na camada física (cabeamento de rede), como assegurar que cada "pacote" da mensagem seja entregue em ordem?</p>
<p>É pra isto que foi criado o <strong>modelo de comunicação OSI</strong>.</p>
<h3>Modelo OSI</h3>
<p>OSI é um modelo de referência para comunicação entre diferentes dispositivos através de diferentes redes, que estabelece um conjunto de camadas que vai desde a camada física até a camada de formato de mensagens.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sdfapddkf7j5uzojvgzg.png" alt="modelo OSI" /></p>
<ul>
<li>
<p><strong>Camada física</strong>: responsável pelo tráfego de informações através de meios físicos, tais como bluetooth, frequência de rádio, cabos etc</p>
</li>
<li>
<p><strong>Camada de enlace de rede</strong>: responsável pela decodificação e codificação de mensagens em frames, do meio físico para o meio digital e vice-versa</p>
</li>
<li>
<p><strong>Camada de rede</strong>: é aqui que definimos protocolos de rede, tais como o <em>protocolo de internet</em>, também conhecido como <strong>IP</strong> (Internet Protocol)</p>
</li>
</ul>
<blockquote>
<p>Na web, os dados trafegam geralmente através de uma rede de computadores pública, global e descentralizada, neste caso a Internet</p>
</blockquote>
<ul>
<li>
<p><strong>Camada de transporte</strong>: camada responsável por características de entrega, tais como definir critérios de confiabilidade e ordem dos pacotes de mensagens. Por exemplo, nesta camada temos o <em>protocolo de  controle de transmissão</em>, ou <strong>TCP</strong></p>
</li>
<li>
<p><strong>Camada de sessão e apresentação</strong>: aqui vão critérios de informações que podem ser vinculadas a uma determinada conexão entre diferentes dispositivos, bem como o formato de apresentação das informações na rede</p>
</li>
<li>
<p><strong>Camada de aplicação</strong>: nesta camada, temos a definição do formato de mensagens em um nível mais "aplicacional", como por exemplo protocolo HTTP (Hypertext Transfer Protocol), FTP, SSH entre outros</p>
</li>
</ul>
<p>Entretanto fica aqui uma questão: como que todo esse modelo de comunicação em rede se converte em algo prático num programa dentro de um sistema operacional?</p>
<p>Chegou o momento de falar sobre <em>sockets</em> e TCP.</p>
<h3>Sockets e TCP</h3>
<p>Num computador, todos os programas são encapsulados dentro de uma estrutura chamada <em>processo</em>, como vimos em artigos anteriores.</p>
<p>Quando falamos em cliente na aquitetura cliente-servidor, estamos falando de um processo rodando dentro de um computador, e o mesmo vale para o servidor, onde cada processo tem seu próprio identificador, ou <em>PID</em>:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7q9yi7cdbs42p0352s24.png" alt="pids" /></p>
<p>Sabendo que processos são isolados, foram definidas diferentes formas de comunicação entre processos (também conhecido como IPC, ou <em>inter process communication</em>), tais como pipes, arquivos do filesystem, descritores de arquivos e UNIX sockets.</p>
<blockquote>
<p>Estamos baseando a saga em sistema "UNIX-like", mais especificamente GNU/Linux</p>
</blockquote>
<p>Ou seja, temos ciência que é possível fazer 2 processos <em>dentro de um mesmo computador</em> se comunicarem através de UNIX sockets. Mas como fazer dois processos em computadores distintos se comunicarem?</p>
<p>Entramos então em <strong>Berkeley Sockets</strong>, que define uma API comum de comunicação utilizando sockets, onde diferentes sockets podem estar no mesmo computador, ou em uma mesma rede local, ou até mesmo em redes diferentes dentro da <em>Internet</em>.</p>
<p>É aqui que temos a introdução ao TCP, que é um protocolo de comunicação via sockets. Portanto, para fazer um cliente se comunicar com um servidor, é preciso estabelecer <em>endpoints de comunicação</em>, que são basicamente <strong>sockets</strong>, e neste caso para a web, vamos utilizar <em>sockets TCP</em>.</p>
<p>Estes sockets são abertos tanto do lado do cliente, quanto no servidor. No servidor, estes sockets são mapeados em descritores de arquivos, que representam um número especial e reservado, também chamado de <strong>porta de comunicação</strong>:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j1x249n2k8ajhgvj2fsq.png" alt="sockets e tcp" /></p>
<blockquote>
<p>Ok Leandro, consegui entender o conceito de sockets e TCP. Mas qual deveria ser o formato da mensagem na web?</p>
</blockquote>
<p>Com vocês, o <em>HTTP</em>.</p>
<h3>HTTP</h3>
<p>HTTP é um protocolo de formato de mensagem que faz parte da camada de aplicação.</p>
<p>Com HTTP, a mensagem é definida seguindo padrões de hipertexto, que são basicamente documentos que podem ter ligações com outros documentos em sites diferentes.</p>
<p>Na web, o padrão segue um formato de <em>headline</em>, que contém o tipo de pedido, seguido de quebra de linhas com <em>cabeçalhos</em> de metadados e por fim, opcionalmente e dependendo do tipo de pedido, um <em>corpo</em> com a mensagem principal contendo majoritariamente HTML, CSS e Javascript.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7d0w16gxgauxlw4bnwe6.png" alt="tcp &amp; http" /></p>
<p>Até agora, passamos por conceitos que formam a web. Como nosso exemplo de web server é bastante simples, estes fundamentos já são o suficiente para entrarmos na próxima seção, que é de fato escrever o web server em Assembly x86.</p>
<hr />
<h2>Como funciona um servidor web</h2>
<p>Conforme vimos na seção anterior, arquitetura web passa por manipulação de sockets TCP.</p>
<p>Tal manipulação é feita via <em>chamadas de sistema</em> (syscalls) no sistema operacional, portanto, para darmos início ao servidor, vamos entender como devem ser criados os sockets a nível do OS.</p>
<h3>4 syscalls para o resgate</h3>
<p>Resumidamente temos que fazer 4 syscalls para termos um server operante, que são:</p>
<p><strong>socket</strong>
A syscall <em>socket</em> é responsável por criar um endpoint de comunicação de rede e retornar um descritor de arquivo (fd) relativo ao endpoint criado.</p>
<p>Na libc, <em>socket</em> é referenciada pelo número 41 e tem a seguinte assinatura:</p>
<pre><code class="language-c">int socket(int domain, int type, int protocol)
</code></pre>
<blockquote>
<p>Lembrando que estamos utilizando arquitetura x86_64, ou x64</p>
</blockquote>
<p><strong>bind</strong>
<em>bind</em> atribui nome e porta ao socket previamente criado. Esta syscall na libc responde pelo número 49 e tem a assinatura a seguir:</p>
<pre><code class="language-c">int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
</code></pre>
<p><strong>listen</strong>
A syscall <em>listen</em> marca o socket criado (precisa ser do tipo stream, no caso TCP) para aceitar conexões. É conhecida pelo número 50 e tem a seguinte assinatura em C:</p>
<pre><code class="language-c">int listen(int sockfd, int backlog)
</code></pre>
<p><strong>accept</strong>
A syscall <em>accept</em> admite uma conexão de um cliente no socket e cria um <em>novo socket de conexão específico</em> para aquele cliente. Esta syscall, a princípio, bloqueia o programa e só continua a execução quando uma nova conexão com novo cliente é estabelecida.</p>
<p>É referenciada pelo número 288 e tem a seguinte assintaura:</p>
<pre><code class="language-c">int accept(int sockfd, struct *addr, int addrlen, int flags)
</code></pre>
<p>Em resumo, tudo o que precisamos para criar um web server, independente do programa, linguagem de programação ou tecnologia, é de chamar estas 4 syscalls.</p>
<blockquote>
<p>Não se engane, o teu servidor Express, Rails, Django ou NGINX, faz estas chamadas de sistema por baixo dos panos: <em>socket, bind, listen e accept</em></p>
</blockquote>
<p>Sem mais delongas, vamos ver como tudo isto se aplica naquilo que importa para esta saga: <strong>assembly</strong>.</p>
<hr />
<h2>Um server modesto em Assembly</h2>
<p>Montar as syscalls para o web server em Assembly não é tão difícil quanto parece. Para começar, vamos fazer a primeira syscall, que é a <em>socket</em>.</p>
<h3>Criando o socket</h3>
<p>Como de costume, vamos montar as instruções de acordo com o <a href="https://man7.org/linux/man-pages/man2/socket.2.html">manual</a> e <a href="https://x64.syscall.sh/">tabela de syscalls</a>.</p>
<blockquote>
<p>Já vimos na seção anterior quais são os números das syscalls e suas respectivas assinaturas na libc</p>
</blockquote>
<p>Iniciamos definindo as constantes, apenas as necessárias para a syscall <em>socket</em>:</p>
<pre><code class="language-as">global _start

; syscalls constants
%define SYS_socket 41

; other constants
%define AF_INET 2
%define SOCK_STREAM 1
%define SOCK_PROTOCOL 0
</code></pre>
<p>Após isto, vamos reservar 1 byte com a diretiva <code>resb 1</code> que significa "reservar 1 byte". Este byte será utilizado para armazenar o número do descritor de arquivo que referencia o <em>socket</em> que vai ser criado.</p>
<p>Como <strong>não queremos inicializar</strong> o valor deste byte, não vamos colocar na seção <code>.data</code> como temos utilizado até o momento na saga, mas sim na seção <code>.bss</code>.</p>
<ul>
<li>
<p>Na seção <code>.data</code>, ficam apenas dados inicializados</p>
</li>
<li>
<p>Na seção <code>.bss</code>, ficam os dados não-inicializados</p>
</li>
</ul>
<pre><code class="language-as">section .bss
sockfd: resb 1
</code></pre>
<p>Vamos relembrar o layout de memória:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3wznvwyjq68cs1ln7aj3.png" alt="layout de memória" /></p>
<p>Como vemos na imagem, a seção <code>.bss</code> vem a seguir a seção <code>.data</code>, ou seja, fica em endereços de memória mais altos que a seção <code>.data</code>.</p>
<p>Agora, vamos montar os registradores seguindo a convenção de chamada e a ordem dos parâmetros da função <em>socket</em> na libc:</p>
<pre><code class="language-as">section .text
_start:
.socket:
	; int socket(int domain, int type, int protocol)
	mov rdi, AF_INET
	mov rsi, SOCK_STREAM
	mov rdx, SOCK_PROTOCOL
	mov rax, SYS_socket
	syscall
	mov [sockfd], rax 
.exit:
	mov rdi, 0
	mov rax, 60
	syscall
</code></pre>
<ul>
<li>
<p><strong>domain</strong>: representa o domínio de comunicação. No caso queremos usar AF_INET, que significa IPv4, e tem o valor 2 conforme especificado no <a href="https://github.com/bminor/glibc/blob/8f58e412b1e26d2c7e65c13a0ce758fbaf18d83f/bits/socket.h#L78">glibc</a></p>
</li>
<li>
<p><strong>type</strong>: representa o tipo de comunicação, que no caso vamos usar SOCK_STREAM que é sequencial, confiável, duplex e baseado em conexão. O valor <a href="https://github.com/bminor/glibc/blob/8f58e412b1e26d2c7e65c13a0ce758fbaf18d83f/bits/socket.h#L42">conforme glibc</a> é 1</p>
</li>
<li>
<p><strong>protocol</strong>: esta opção é usada no caso da utilização de um protocolo em específico. Neste caso, vamos deixar o valor como 0 que é o default para AF_INET e SOCK_STREAM, <em>indicando que se trata de um socket TCP</em></p>
</li>
</ul>
<blockquote>
<p>Lembrando que existem sockets da família UNIX que não funcionam na camada de rede IP. É possível combinar socket UNIX com SOCK_STREAM, mas neste caso estamos combinando a família AF_INET (IPv4) com o tipo SOCK_STREAM (segmento de bytes, duplex), e esta combinação faz este socket ser TCP. Para mais detalhes sobre sockets, sugiro a leitura de um artigo que escrevi sobre <a href="https://leandronsp.com/articles/building-a-web-server-in-bash-part-i-sockets-2n8b">UNIX Sockets</a></p>
</blockquote>
<p>Vamos confirmar com GDB?</p>
<pre><code class="language-bash"># Breakpoint na linha &lt;syscall&gt;
(gdb) break 22

(gdb) run

# Confirmando que os registradores estão com os valores corretos
# antes da execução da syscall...
(gdb) i r rdi rsi rdx rax
rdi            0x2                 2
rsi            0x1                 1
rdx            0x0                 0
rax            0x29                41

# Confirmando que `sockfd` continua com o valor zerado
(gdb) x &amp;sockfd
0x402000 &lt;sockfd&gt;:      0x00000000

(gdb) next
</code></pre>
<p>Após a execução da syscall, podemos ver que o <em>retorno da função</em>, que representa o descritor de arquivo conforme documentação, está armazenado no registrador RAX (de acordo com a convenção de chamada):</p>
<pre><code class="language-as">(gdb) i r rax
rax            0x3                 3

(gdb) next

(gdb) x &amp;sockfd
0x402000 &lt;sockfd&gt;:      0x00000003
</code></pre>
<p>Ou seja, após a syscall, temos em <code>sockfd</code> o número do socket que acabou de ser criado.</p>
<p>Executando com <em>strace</em>:</p>
<pre><code class="language-bash">$ strace ./live

execve("./live", ["./live"], 0x7ffca20187e0 /* 24 vars */) = 0
socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3
exit(0)                                 = ?
+++ exited with 0 +++
</code></pre>
<p>Sem erros, <em>yay!</em></p>
<p>Vamos para a próxima syscall.</p>
<h3>Fazendo bind no socket</h3>
<p>Agora, é o momento de atribuir um endereço e uma porta como endpoint de comunicação para este socket. É para isto que serve a syscall <em>bind</em>.</p>
<p>Analisando a função:</p>
<pre><code class="language-c">; int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
</code></pre>
<p>Podemos ver que um dos argumentos é um <em>ponteiro</em> para uma struct na memória. Vamos entender melhor cada argumento.</p>
<p><strong>sockfd</strong>
Em sockfd vai o inteiro que representa o descritor do socket criado</p>
<p>*<em>sockaddr <strong>addr</strong></em>
Representa o ponteiro para o endereço de memória que contém uma estrutura de dados que, de acordo com <a href="https://www.gta.ufrj.br/ensino/eel878/sockets/sockaddr_inman.html">este guia</a>, contempla: <em>family, port, ip_address, sin_zero</em>, onde sin_zero é apenas padding de preenchimento de bytes.</p>
<p>Para arquitetura x64, esta estrutura deve conter 16 bytes no total, onde:</p>
<ul>
<li>
<p>2 bytes são para a <em>família</em> de protocolo</p>
</li>
<li>
<p>2 bytes para a <em>porta</em></p>
</li>
<li>
<p>4 bytes para o <em>endereço de IP</em></p>
</li>
<li>
<p>8 bytes de padding para o <em>sin_zero</em>, ou seja, preencher os 8 bytes restantes com ZERO</p>
</li>
</ul>
<p><strong>addrlen</strong>: tamanho do sockaddr, e já sabemos que são 16 bytes</p>
<p>Uma vez entendidos os parâmetros da função, vamos montar a chamada.</p>
<pre><code class="language-as">%define SYS_bind 49

; Data types in asm
; (db) byte =&gt; 1 byte
; (dw) word =&gt; 2 bytes
; (dd) doubleword =&gt; 4 bytes
; (dq) quadword =&gt; 8 bytes

section .data
sockaddr: 
	family: dw AF_INET   ; 2 bytes
	port: dw 0x0BB8      ; 2 bytes (representa a porta 3000)
	ip_address: dd 0     ; 4 bytes
	sin_zero: dq 0       ; 8 bytes

.bind:
	; int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
	mov rdi, [sockfd]
	mov rsi, sockaddr
	mov rdx, 16
	mov rax, SYS_bind
	syscall
</code></pre>
<p>Ao validar com GDB, podemos ver que o <code>sockaddr</code> está armazenando a estrutura necessária para ser enviada no parâmetro <code>sockaddr *addr</code> da syscall:</p>
<pre><code class="language-bash"># Breakpoint na syscall de bind
(gdb) break 38

(gdb) run

(gdb) x &amp;sockaddr
0x402000 &lt;family&gt;:      0xb80b0002
</code></pre>
<p>Se buscarmos os 2 primeiros bytes, confirmamos que é o valor 2 (repare que está invertido pois é o padrão little-endian da aquitetura x86_64:</p>
<pre><code class="language-bash">(gdb) x /2xb &amp;sockaddr
0x402000 &lt;family&gt;:      0x02    0x00
</code></pre>
<p>Quanto à porta, queremos que o server responda no número 3000. Portanto, verificamos que os próximos 2 bytes representam a porta:</p>
<pre><code class="language-bash"># Em hexadecimal, 3000 equivale a 0x0BB8, mas por causa do formato
# little-endian da arquitetura x86_64, estamos visualizando 0xB80B
(gdb) x /2xb (void*) &amp;sockaddr+2
0x402002 &lt;port&gt;:        0xb8    0x0b
</code></pre>
<p>Queremos também que o servidor responda no endereço de IP <code>0.0.0.0</code>, então os próximos 4 bytes estarão todos a zero:</p>
<pre><code class="language-bash">(gdb) x /4xb (void*) &amp;sockaddr+4
0x402004 &lt;ip_address&gt;:  0x00    0x00    0x00    0x00
</code></pre>
<p>E, por fim, os 8 bytes restantes representando <em>sin_zero</em>, todos preenchidos com zero:</p>
<pre><code class="language-bash">(gdb) x /8xb (void*) &amp;sockaddr+8
0x402008 &lt;sin_zero&gt;:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
</code></pre>
<p>Vamos executar com <em>strace</em>:</p>
<pre><code class="language-bash">$ strace ./live

execve("./live", ["./live"], 0x7ffd51ed4650 /* 24 vars */) = 0
socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3
bind(3, {sa_family=AF_INET, sin_port=htons(47115), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
exit(0)                                 = ?
+++ exited with 0 +++
</code></pre>
<p>Ouch! Apesar da função <em>bind</em> ter retornado 0 indicando que não houve erros, temos um pequeno problema. Repare que a <em>porta</em> não está sendo mapeada para o número <strong>3000</strong>, e sim para <strong>47115</strong>, conforme vemos em <em>htons(47115)</em>.</p>
<p><strong>Entendendo a implicação de endianess na syscall bind</strong>
<code>htons</code> é uma função de rede utilizada para converter a ordem dos bytes do programa antes de serem utilizados na rede. Como a internet utiliza big-endian, esta função converte a ordem utilizada na arquitetura (no caso da x86_64, little-endian) para o formato big-endian da rede.</p>
<p>Entretanto <em>htons(47115)</em> não é o valor que queremos. O que precisamos é que o mapeamendo seja <em>htons(3000)</em>. Por quê isto está acontecendo?</p>
<p>O valor que colocamos em hexadecimal representando <em>3000</em> é <em>0x0BB8</em>, mas se prestarmos atenção no GBD, o valor de fato armazenado está com os bytes invertidos para little-endian, que é <em>0xB80B</em>. Ocorre que <code>0xB80B</code> em decimal é <strong>47115</strong>!!!!!! Aí que está o problema!</p>
<p>Precisamos então inverter os bytes no programa, e assim sendo o valor que será passado para a função htons fica corrigido.</p>
<pre><code class="language-as">....
section .data
sockaddr: 
	family: dw AF_INET   ; 2 bytes
	port: dw 0xB80B      ; 2 bytes (aqui invertemos os bytes)
	ip_address: dd 0     ; 4 bytes
	sin_zero: dq 0       ; 8 bytes
....
</code></pre>
<p>E analisando novamente com GDB:</p>
<pre><code class="language-bash"># Agora sim, apesar de estar invertido, é exatamente este valor que
# queremos que seja passado para htons: 0x0BB8 em decimal é 3000
(gdb) x /2xb (void*) &amp;sockaddr+2
0x402002 &lt;port&gt;:        0x0b    0xb8
</code></pre>
<p>Executando novamente com <em>strace</em>:</p>
<pre><code class="language-bash">$ strace ./live

execve("./live", ["./live"], 0x7ffd51ed4650 /* 24 vars */) = 0
socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3
bind(3, {sa_family=AF_INET, sin_port=htons(3000), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
exit(0)                                 = ?
+++ exited with 0 +++
</code></pre>
<p><em>Superb!</em> Podemos ver que a syscall <em>bind</em> foi executada com os parâmetros corretamente, inclusive o <code>htons(3000)</code>, então retornando <em>0</em>, que indica que não houve qualquer erro.</p>
<h3>Preparando para receber conexões</h3>
<p>Próximo passo consiste em preparar o socket para receber conexões, que basicamente é chamar a função <code>listen</code>:</p>
<pre><code class="language-as">%define SYS_listen 50
%define BACKLOG 2

.listen:
	; int listen(int sockfd, int backlog)
	mov rdi, [sockfd]
	mov rsi, BACKLOG
	mov rax, SYS_listen
	syscall
</code></pre>
<p>Onde <em>BACKLOG</em> significa a quantidade de conexões "pendentes" no socket. Executamos com <em>strace</em> e:</p>
<pre><code class="language-bash">$ strace ./live

execve("./live", ["./live"], 0x7ffe6b4eea30 /* 24 vars */) = 0
socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3
bind(3, {sa_family=AF_INET, sin_port=htons(3000), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
listen(3, 2)                            = 0
exit(0)                                 = ?
+++ exited with 0 +++
</code></pre>
<p><em>Que noite maravilhosa!</em> Listen funcionou lindamente, afinal, é uma função muito simples. Agora, hora de aceitar conexões de clientes no socket.</p>
<h3>Chegou o momento de aceitar clientes</h3>
<p>O grande momento chegou. Vamos montar as instruções da syscall <em>accept</em>, que de acordo com a função em libc, recebe um socket como primeiro argumento e os demais são opcionais.</p>
<pre><code class="language-as">%define SYS_accept 288

.accept:
	; int accept(int sockfd, struct *addr, int addrlen, int flags)
	mov rdi, [sockfd]
	mov rsi, 0              ; não precisa estabelecer um addr
	mov rdx, 0              ; não precisa do tamanho uma vez que não há addr
	mov r10, 0
	mov rax, SYS_accept
	syscall
</code></pre>
<p>Se executarmos com GDB, podemos ver que o resultado da syscall fica bloqueado até que uma conexão seja feita:</p>
<pre><code class="language-bash"># Breakpoint na syscall de socket
(gdb) break 55

(gdb) run
(gdb) next
</code></pre>
<p>O programa está parado na syscall de socket, aguardando resposta do <strong>kernel</strong>. Para que o kernel responda e o programa continue a execução, é preciso realizar um pedido usando um HTTP client, e neste caso vamos usar o <em>curl</em>:</p>
<pre><code class="language-bash">$ curl localhost:3000
</code></pre>
<p>Repare que o programa continuou a execução. Vamos ver a resposta que está em RAX:</p>
<pre><code class="language-bash">(gdb) i r rax
rax            0x4                 4

# Um número diferente do sockfd, que é o socket criado pelo server
(gdb) x &amp;sockfd
0x402010 &lt;sockfd&gt;:      0x00000003

</code></pre>
<p>Podemos ver que é um número diferente (RAX contém 4 e sockfd contém 3). De acordo com a documentação, este é o número do descritor que representa um novo socket criado para comunicação entre <em>um cliente específico e o servidor</em>.</p>
<p>Vamos mover o valor de RAX para R8, apenas para preservar o socket, uma vez que RAX será usado novamente por outras syscalls de accept:</p>
<pre><code class="language-as">mov r8, rax             ; client socket
</code></pre>
<h3>Resposta do servidor e fechamento da conexão</h3>
<p>Uma outra coisa importante a se fazer é <strong>fechar a conexão</strong> com este socket do cliente depois de ter processado e respondido a requisição.</p>
<p>Vamos implementar a subrotina <code>.write</code>, que escreve a resposta na conexão (socket) do cliente:</p>
<pre><code class="language-as">%define SYS_write 1

%define CR 0xD
%define LF 0xA

section .data
response: 
	headline: db "HTTP/1.1 200 OK", CR, LF
	content_type: db "Content-Type: text/html", CR, LF
	content_length: db "Content-Length: 22", CR, LF
	crlf: db CR, LF
	body: db "&lt;h1&gt;Hello, World!&lt;/h1&gt;"
responseLen: equ $ - response

section .text
...
.write:
	; int write(int fd, buffer *bf, int bfLen)
	mov rdi, r8
	mov rsi, response
	mov rdx, responseLen
	mov rax, SYS_write
	syscall
	ret
</code></pre>
<p>No exemplo acima, assumimos que a string de resposta HTTP aponta para uma estrutura na memória, definida em <code>.data</code>.</p>
<blockquote>
<p>Atenção para CR (carriage return), LF (line feed) que são constantes que representam <code>\r\n </code> que são separadores de linhas definidos pelo protocolo HTTP</p>
</blockquote>
<p>Agora, definir a subrotina <code>.close</code>, que fecha a conexão com o cliente:</p>
<pre><code class="language-as">%define SYS_close 3

section .text
...
.close:
	; int close(int fd)
	mov rdi, r8
	mov rax, SYS_close
	syscall
	ret
</code></pre>
<p>Ligando tudo no <em>accept</em>:</p>
<pre><code class="language-as">section .text
....
.accept:
	; int accept(int sockfd, struct *addr, int addrlen, int flags)
	mov rdi, [sockfd]
	mov rsi, 0              ; não precisa estabelecer um addr
	mov rdx, 0              ; não precisa do tamanho uma vez que não há addr
	mov r10, 0
	mov rax, SYS_accept
	syscall
	mov r8, rax             ; client socket
	call .write             ; escreve no socket
	call .close             ; fecha o socket
	jmp .exit               ; termina o programa
</code></pre>
<p>E agora, vamos executar o programa com <em>strace</em>:</p>
<pre><code class="language-bash">$ strace ./live

execve("./live", ["./live"], 0x7ffd811567c0 /* 24 vars */) = 0
socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3
bind(3, {sa_family=AF_INET, sin_port=htons(3000), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
listen(3, 2)                            = 0
accept4(3, NULL, NULL, 0
</code></pre>
<ul>
<li>
<p>primeiro foi feita a syscall <em>socket</em></p>
</li>
<li>
<p>a seguir foi feito o <em>bind</em></p>
</li>
<li>
<p>depois o <em>listen</em></p>
</li>
<li>
<p>e por fim, o <em>accept</em> ficou bloqueado a espera de uma requisição</p>
</li>
</ul>
<p>Em outra janela, vamos fazer a requisição:</p>
<pre><code class="language-bash">$ curl localhost:3000
&lt;h1&gt;Hello, World!&lt;/h1&gt;
</code></pre>
<p>E no servidor, a saída do strace no final ficou assim:</p>
<pre><code class="language-bash">write(4, "HTTP/1.1 200 OK\r\nContent-Type: t"..., 86) = 86
close(4)                                = 0
exit(0)                                 = ?
+++ exited with 0 +++
</code></pre>
<p>Escreveu a resposta com <code>write</code>, fechou a conexão com <code>close</code>, e depois terminou o programa com <code>exit</code>.</p>
<p><em>Como não ficar feliz?</em></p>
<h3>Mas o servidor deve ficar em loop, não?</h3>
<p>Sim, o servidor deve ficar em loop, portanto ao invés de fazer o <code>jmp .exit</code>, fazemos <code>jmp .accept</code> na última linha da procedure:</p>
<pre><code class="language-as">...
.accept:
	; int accept(int sockfd, struct *addr, int addrlen, int flags)
	mov rdi, [sockfd]
	mov rsi, 0              ; não precisa estabelecer um addr
	mov rdx, 0              ; não precisa do tamanho uma vez que não há addr
	mov r10, 0
	mov rax, SYS_accept
	syscall
	mov r8, rax             ; client socket
	call .write
	call .close
	jmp .accept             ; &lt;-- MUDANÇA AQUI, mantém o server em loop infinito
</code></pre>
<p>Assim, o server nunca termina, e quando uma conexão com um cliente é fechada, voltamos no início do loop e ficamos a espera de nova conexão na syscall <em>accept</em>.</p>
<p>Código final do server:</p>
<pre><code class="language-as">global _start

%define SYS_socket 41
%define SYS_bind 49
%define SYS_listen 50
%define SYS_accept 288
%define SYS_write 1
%define SYS_close 3

%define AF_INET 2
%define SOCK_STREAM 1
%define SOCK_PROTOCOL 0
%define BACKLOG 2
%define CR 0xD
%define LF 0xA

; Data types in asm
; byte =&gt; 1 byte
; word =&gt; 2 bytes
; doubleword =&gt; 4 bytes
; quadword =&gt; 8 bytes

section .data
sockaddr: 
	family: dw AF_INET   ; 2 bytes
	port: dw 0xB80B      ; 2 bytes (47115 big endian becomes 3000 little endian)
	ip_address: dd 0     ; 4 bytes
	sin_zero: dq 0       ; 8 bytes
sockaddrLen: equ $ - sockaddr
response: 
	headline: db "HTTP/1.1 200 OK", CR, LF
	content_type: db "Content-Type: text/html", CR, LF
	content_length: db "Content-Length: 22", CR, LF
	crlf: db CR, LF
	body: db "&lt;h1&gt;Hello, World!&lt;/h1&gt;"
responseLen: equ $ - response

section .bss
sockfd: resb 1

section .text
_start:
.socket:
	; int socket(int domain, int type, int protocol)
	mov rdi, AF_INET
	mov rsi, SOCK_STREAM
	mov rdx, SOCK_PROTOCOL
	mov rax, SYS_socket
	syscall
	mov [sockfd], rax 
.bind:
	; int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
	mov rdi, [sockfd]
	mov rsi, sockaddr
	mov rdx, sockaddrLen
	mov rax, SYS_bind
	syscall
.listen:
	; int listen(int sockfd, int backlog)
	mov rdi, [sockfd]
	mov rsi, BACKLOG
	mov rax, SYS_listen
	syscall
.accept:
	; int accept(int sockfd, struct *addr, int addrlen, int flags)
	mov rdi, [sockfd]
	mov rsi, 0              ; não precisa estabelecer um addr
	mov rdx, 0              ; não precisa do tamanho uma vez que não há addr
	mov r10, 0
	mov rax, SYS_accept
	syscall
	mov r8, rax             ; client socket
	call .write
	call .close
	jmp .accept
.write:
	; int write(int fd, buffer *bf, int bfLen)
	mov rdi, r8
	mov rsi, response
	mov rdx, responseLen
	mov rax, SYS_write
	syscall
	ret
.close:
	; int close(int fd)
	mov rdi, r8
	mov rax, SYS_close
	syscall
	ret
</code></pre>
<p>Executando tudo com <em>strace</em> e temos:</p>
<pre><code class="language-bash">$ strace ./live

execve("./live", ["./live"], 0x7fff9fde7840 /* 24 vars */) = 0
socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3
bind(3, {sa_family=AF_INET, sin_port=htons(3000), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
listen(3, 2)                            = 0
accept4(3, NULL, NULL, 0)               = 4
write(4, "HTTP/1.1 200 OK\r\nContent-Type: t"..., 86) = 86
close(4)                                = 0
accept4(3, NULL, NULL, 0)               = 4
write(4, "HTTP/1.1 200 OK\r\nContent-Type: t"..., 86) = 86
close(4)                                = 0
accept4(3, NULL, NULL, 0)               = 4
write(4, "HTTP/1.1 200 OK\r\nContent-Type: t"..., 86) = 86
close(4)                                = 0
accept4(3, NULL, NULL, 0
</code></pre>
<p>No lado do cliente:</p>
<pre><code class="language-bash">$ curl localhost:3000
&lt;h1&gt;Hello, World!&lt;/h1&gt;

$ curl localhost:3000
&lt;h1&gt;Hello, World!&lt;/h1&gt;

$ curl localhost:3000
&lt;h1&gt;Hello, World!&lt;/h1&gt;
</code></pre>
<h3>Com vocês, o web browser</h3>
<p>Esta saga não teria nenhuma graça se não fosse pra ser executada em um <em>web browser</em>, afinal estamos falando de um <strong>web server</strong>, não?</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qm4v5vqi8jw5578sajjy.png" alt="final hello world" /></p>
<hr />
<h2>Conclusão</h2>
<p>Incrivelmente chegamos no final da construção de um modesto web server. Aqui aprendemos conceitos sobre sockets, TCP e HTTP, com uma pitada leve de HTML.</p>
<blockquote>
<p>Fala aí, quem não já conhecia a tag H1 do HTML? kk</p>
</blockquote>
<p>Para além de termos visto sobre as syscalls de rede <em>socket, bind, listen e accept</em> em Assembly.</p>
<p>Ainda não chegamos ao fim da saga, pelo que no próximo artigo iremos abordar a criação de <strong>threads</strong> e aprender sobre alocação dinâmica de memória para as threads.</p>
<p>Stay tuned!</p>
<p><em>Agradecimentos a <a href="https://x.com/rodrigogbranco">Rodrigo Gonçalves de Branco</a> por ter revisado este artigo com o devido rigor</em></p>
<hr />
<h2>Referências</h2>
<sub>
Building a web server in Bash
https://leandronsp.com/articles/tekton-ci-part-i-a-gentle-introduction-ilj
OSI Model
https://en.wikipedia.org/wiki/OSI_model
TCP
https://en.wikipedia.org/wiki/Transmission_Control_Protocol
Berkeley Sockets
https://en.wikipedia.org/wiki/Berkeley_sockets
HTTP
https://en.wikipedia.org/wiki/HTTP
struct sockaddr_in
https://www.gta.ufrj.br/ensino/eel878/sockets/sockaddr_inman.html
</sub>
]]></description>
<pubDate>2024-05-25</pubDate>
</item>
<item>
<title>Construindo um web server em Assembly x86, parte IV, um assembly modesto</title>
<link>https://leandronsp.com/articles/construindo-um-web-server-em-assembly-x86-parte-iv-um-assembly-modesto-oif.html</link>
<guid>https://leandronsp.com/articles/construindo-um-web-server-em-assembly-x86-parte-iv-um-assembly-modesto-oif.html</guid>
<description><![CDATA[<p>Uma vez que temos <a href="https://leandronsp.com/articles/construindo-um-web-server-em-assembly-x86-parte-iii-codigo-de-maquina-bgk">uma compreensão</a> sobre sistema binário, hexadecimal, ASCII e código de máquina, chegou o grande momento de entrarmos no assunto principal desta saga: <strong>assembly</strong>.</p>
<p>Vamos iniciar transportando o "Hello, World" feito em código de máquina para assembly x86 e, posteriormente, abordar um exemplo de programa que recebe argumento da linha de comando.</p>
<p>Ao longo deste artigo vamos aprender a base de conceitos como rótulos, segmentos de memória, <em>muito gdb</em>, layout de memória, <strong>muita stack</strong>, procedures (subrotinas, ou <em>funções</em>), loops, condicionais, flags, tipos de registradores e etc</p>
<p>Aperte os cintos, pois este será um artigo bem extenso. Sugiro ao leitor, - que tem interesse em aprender na prática com esta saga -, que tenha o ambiente preparado e que execute cada exemplo seguindo os passos aqui descritos.</p>
<p>Sem mais delongas, vamos ao que importa.</p>
<hr />
<h2>Agenda</h2>
<ul>
<li>
<p><a href="#humanizar-%C3%A9-preciso">Humanizar é preciso</a></p>
<ul>
<li><a href="#mnemonics">Mnemonics</a></li>
</ul>
</li>
<li>
<p><a href="#assemblers">Assemblers</a></p>
</li>
<li>
<p><a href="#nosso-primeiro-programa">Nosso primeiro programa</a></p>
<ul>
<li><a href="#a-chamada-de-sistema-exit">A chamada de sistema exit</a></li>
<li><a href="#arquivos-objeto">Arquivos Objeto</a></li>
<li><a href="#linker">Linker</a></li>
</ul>
</li>
<li>
<p><a href="#depurando-o-programa">Depurando o programa</a></p>
<ul>
<li><a href="#o-utilit%C3%A1rio-size">O utilitário size</a></li>
<li><a href="#debugging-com-gdb">Debugging com gdb</a></li>
<li><a href="#rastreando-execu%C3%A7%C3%A3o-com-strace">Rastreando execução com strace</a></li>
</ul>
</li>
<li>
<p><a href="#evoluindo-nosso-primeiro-programa">Evoluindo nosso primeiro programa
</a></p>
<ul>
<li><a href="#alocando-bytes-para-hello-world">Alocando bytes para "Hello, World"</a></li>
<li><a href="#adicionando-a-chamada-de-sistema-write">Adicionando a chamada de sistema write</a></li>
<li><a href="#layout-de-mem%C3%B3ria">Layout de memória</a></li>
<li><a href="#definindo-constantes">Definindo constantes</a></li>
</ul>
</li>
<li>
<p><a href="#um-programa-mais-sofisticado">Um programa mais sofisticado</a></p>
<ul>
<li><a href="#definindo-labels">Definindo labels</a></li>
<li><a href="#desvio-de-fluxo-com-jump">Desvio de fluxo com jump</a></li>
<li><a href="#desvio-de-fluxo-com-call">Desvio de fluxo com call</a></li>
<li><a href="#brincando-com-pilhas">Brincando com pilhas</a></li>
<li><a href="#calculando-tamanho-dinamicamente-com-loop">Calculando tamanho dinamicamente com loop</a></li>
<li><a href="#rflags">RFLAGS</a></li>
<li><a href="#botando-mais-pilha-no-neg%C3%B3cio">Botando mais pilha no negócio</a></li>
<li><a href="#depurando-o-programa-final-com-strace">Depurando o programa final com strace</a></li>
</ul>
</li>
<li>
<p><a href="#falando-um-pouco-de-registradores">Falando um pouco de registradores</a></p>
<ul>
<li><a href="#prop%C3%B3sito-dos-registradores">Propósito dos registradores</a></li>
<li><a href="#registradores-de-prop%C3%B3sito-geral">Registradores de propósito geral</a></li>
<li><a href="#registradores-especiais">Registradores especiais</a></li>
<li><a href="#precisamos-sempre-utilizar-todos-os-64-bits">Precisamos sempre utilizar todos os 64 bits?</a></li>
</ul>
</li>
<li>
<p><a href="#uma-side-note-sobre-stack-frames">Uma side note sobre stack frames</a></p>
</li>
<li>
<p><a href="#conclus%C3%A3o">Conclusão</a></p>
</li>
<li>
<p><a href="#refer%C3%AAncias">Referências</a></p>
</li>
</ul>
<hr />
<blockquote>
<p>Antes de iniciar, quero novamente deixar uma menção especial ao excelente <a href="https://www.youtube.com/watch?v=Ej6U-qk0bdE&amp;list=PLXoSGejyuQGohd0arC7jRBqVdQqf5GqKJ">curso gratuito de Assembly x86</a> do <a href="https://www.youtube.com/@debxp">Blau Araújo</a>. É importante reforçar o quanto este material dele é necessário e foi crucial para que eu pudesse fundamentar diversos conceitos explorados ao longo desta saga</p>
</blockquote>
<h2>Humanizar é preciso</h2>
<p>Como vimos no artigo anterior, CPU só entende código de máquina:</p>
<pre><code>48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 0A  ; Hello, World

BF 01 00 00 00     ; RDI ⬅️ 1
48 BE 00 10 40     ; RSI ⬅️ 0x401000
BA 0D 00 00 00     ; RDX ⬅️ 13
B8 01 00 00 00     ; RAX ⬅️ 1
0F 05              ; SYSCALL
BF 00 00 00 00     ; RDI ⬅️ 0
B8 3C 00 00 00     ; RAX ⬅️ 60
0F 05              ; SYSCALL
</code></pre>
<p>Entretanto, para uma pessoa desenvolvedora manter um programa em código de máquina, é preciso ter muita paciência e atenção ao detalhe, pelo que também manter programas assim é muito propenso a bugs.</p>
<p>Precisamos de alguma forma, representar cada instrução em código de máquina em uma linguagem mais "human-friendly".</p>
<h3>Mnemonics</h3>
<p>É aí que entram os <strong>mnemonics</strong>, que são uma forma textual de representar informações visando facilitar a memorização para o cérebro humano.</p>
<p>Ao invés de trabalharmos com <code>BF 01 00 00 00</code>, podemos trocar por <code>MOV RDI, 1</code>, que significa:</p>
<blockquote>
<p>estou movendo o valor imediato 1 para o registrador RDI</p>
</blockquote>
<p>E assim vamos <em>montando</em> instrução por instrução, tal e qual faríamos com código de máquina, mas utilizando uma <em>linguagem</em> de fácil memorização.</p>
<p>Mas a CPU não entende essa "linguagem". Temos de construir um <em>programa</em> que faz a <em>tradução</em> de mnemonics para <em>código de máquina</em>, ou seja, de <code>MOV RSI, 1</code> para <code>BF 01 00 00 00</code>.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m7zzy20fad71wg461cpt.png" alt="assembly" /></p>
<p>Estamos falando de <em>montadores</em>, ou simplesmente <strong>assemblers</strong>.</p>
<hr />
<h2>Assemblers</h2>
<p>Ao longo do tempo, foram desenvolvidos diversos assemblers para diferentes arquiteturas.</p>
<p>Para arquitetura x86, há diversos assemblers já construídos, GNU Assembler (as), NASM, FASM, pra mencionar alguns.</p>
<p>Assemblers para esta arquitetura em específico podem seguir 2 tipos de sintaxe que são predominantes:</p>
<ul>
<li>
<p>AT&amp;T, desenvolvida pela AT&amp;T corporation</p>
</li>
<li>
<p>Intel, desenvolvida pela Intel</p>
</li>
</ul>
<p>Nesta saga, vamos focar no Assembler <strong>NASM</strong> para arquitetura X86 64-bits (x64), com <em>sintaxe Intel</em> e rodando em sistema GNU/Linux, como já mencionamos algumas vezes em artigos anteriores.</p>
<ul>
<li>
<p>Arquitetura x86_64 (x64)</p>
</li>
<li>
<p>Sistema Operacional GNU/Linux (Ubuntu)</p>
</li>
<li>
<p>Assembler NASM 2.16.01</p>
</li>
<li>
<p>GNU ld 2.38 (ligador, ou linker)</p>
</li>
<li>
<p>Debugger GNU gdb 12.1</p>
</li>
<li>
<p>strace 5.16 (tracing de syscalls)</p>
</li>
</ul>
<p>Uma vez que definimos as ferramentas utilizadas, vamos seguir traduzindo o "Hello, World" para asm x86 enquanto entendemos o uso de cada uma delas.</p>
<blockquote>
<p>A partir de agora, quando me referir a Assembly ou simplesmente "asm", leia-se <em>Assembly x86_64</em></p>
</blockquote>
<hr />
<h2>Nosso primeiro programa</h2>
<p>Em Assembly, todo programa deve ter um ponto de entrada, também chamado de <strong>entry point</strong>:</p>
<pre><code class="language-as">global _start

_start:
	; código do programa vai aqui
</code></pre>
<p>E a primeira coisa que nosso programa vai fazer é sair:</p>
<blockquote>
<p>kkkkkkkkkk</p>
</blockquote>
<h3>A chamada de sistema exit</h3>
<p>Brincadeiras à parte, a chamada de sistema que precisamos executar é a <code>exit</code>, definida da seguinte forma no <code>glibc</code>:</p>
<pre><code class="language-c">void _exit(int status);
</code></pre>
<p>Com isto, temos de seguir a lógica para montar as instruções tal como fizemos com os opcodes, que seguindo  <a href="https://x64.syscall.sh/">a mesma tabela de syscalls</a>, é:</p>
<ul>
<li>
<p>nome da syscall vai em RAX</p>
</li>
<li>
<p>primeiro argumento (o status de erro) vai em RDI</p>
</li>
</ul>
<pre><code class="language-as">global _start

_start:
	mov rdi, 0   ; error status
	mov rax, 60  ; nome da syscall: SYS_exit
	syscall       
</code></pre>
<p>Este programa simplesmente faz aquilo que mencionamos no artigo anterior: <em>que todo programa deve terminar</em>.</p>
<ul>
<li>
<p><code>mov rdi, 0</code> move o valor imediato 1 para o registrador RDI; vai representar o error code da syscall <strong>exit</strong>: 0 para término sem erros</p>
</li>
<li>
<p><code>mov rax, 60</code> move o valor imediato 60 para o registrador RAX; vai representar o nome da syscall em si, <strong>exit</strong></p>
</li>
<li>
<p><code>syscall</code> faz a chamada de sistema da syscall <strong>exit</strong>, definida em RAX</p>
</li>
</ul>
<p>Para que o programa seja compilado, precisamos primeiro fazer a "montagem" das instruções com NASM:</p>
<pre><code class="language-bash">$ nasm -f elf64 hello.asm -o hello.o
</code></pre>
<ul>
<li>
<p><code>-f elf64</code>: arquitetura de destino, x64</p>
</li>
<li>
<p><code>hello.asm</code> input, ou seja, o arquivo que contém o código fonte</p>
</li>
<li>
<p><code>-o hello.o</code>: define saída para o arquivo <code>hello.o</code></p>
</li>
</ul>
<blockquote>
<p>Mas o quê é este arquivo <code>hello.o</code>?</p>
</blockquote>
<h3>Arquivos objeto</h3>
<p>Arquivo objeto (<strong>Object File</strong>) é um arquivo que contém código de máquina gerado por um assembler ou compilador.</p>
<p>Porém este arquivo ainda não é um <em>executável</em> final, porque podemos querer combinar com outros arquivos objeto e bibliotecas nativas do SO.</p>
<p>A partir deste arquivo, que geralmente tem a extensão <code>.o</code>, podemos utilizar outro programa para "ligar" com outros arquivos, se necessário, no intuito de gerar um arquivo com código de máquina final e executável.</p>
<p>Este programa se chama <strong>linker</strong>, pelo que utilizaremos a versão padrão do <code>ld</code> que vem com o GNU no nosso sistema operacional GNU/Linux.</p>
<h3>Linker</h3>
<p><em>Linker</em> é o programa responsável por, a partir de um ou mais arquivos objeto, gerar um arquivo final executável com o código de máquina.</p>
<p>Como já geramos anteriormente o arquivo objeto <code>hello.o</code> utilizando o assembler NASM, podemos concluir o processo de compilação do nosso programa fonte asm x86 com <code>ld</code></p>
<pre><code class="language-bash">$ ld hello.o -o hello
</code></pre>
<p>E agora, vamos rodar o binário final executável <code>hello</code>:</p>
<pre><code class="language-bash">$ ./hello
echo $?
0
</code></pre>
<p><em>Hurray!</em> Nosso primeiro programa em Assembly concluído com sucesso!</p>
<p>Contudo, vamos lembrar de um ponto importante que vimos na parte II da saga: que o <em>programa e seus dados ficam na memória</em>. Queremos entender <strong>o que está acontecendo na memória</strong> com este simples programa.</p>
<hr />
<h2>Depurando o programa</h2>
<p>Uma das etapas mais importantes, senão a mais  importante, em desenvolvimento de software, é a <em>depuração</em> (ou debugging, em inglês).</p>
<p>Depurar é o ato de conseguir interceptar a execução do programa, analisar o estado, alterar o estado, adicionar pontos de parada (breakpoints) entre outras técnicas.</p>
<p>O processo de depuração também consiste em analisar a saída do programa como um todo, seu tamanho e trace de chamadas no sistema operacional.</p>
<h3>O utilitário size</h3>
<p>Vamos iniciar o processo de depuração do nosso programa analisando o tamanho, com o utilitário GNU <em>size</em>:</p>
<pre><code class="language-bash">$ size hello

   text    data     bss     dec     hex filename
     12       0       0      12       c hello
</code></pre>
<blockquote>
<p>Mas o quê significa "text, data, bss, etc"?</p>
</blockquote>
<p>Cada programa no sistema operacional é dividido em <em>seções</em>, que representam alguma característica para o sistema operacional.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lv9edus1e9nxw4b24cxq.png" alt="layout de memória" /></p>
<p><strong>text</strong>
Esta seção contém todo o código fonte do programa, e assim o SO sabe que precisa buscar esta seção na memória principal</p>
<p><strong>data</strong>
Seção de dados inicializados na memória</p>
<p><strong>bss</strong>
Seção de dados não-inicializados na memória</p>
<p>O comando <strong>size</strong> traz justamente o tamanho (em bytes) de cada seção.</p>
<blockquote>
<p><code>dec</code> e <code>hex</code> não são seções, são apenas a representação do valor total (em bytes) tanto em decimal quanto hexadecimal</p>
</blockquote>
<ul>
<li>
<p><code>text</code>: esta seção contém todo o código fonte do nosso programa, também chamado de "texto"</p>
</li>
<li>
<p><code>data</code>: seção de dados inicializados, logo a seguir neste artigos entramos em detalhe</p>
</li>
<li>
<p><code>bss</code>: seção de dados não-inicializados, logo a seguir também falaremos deste</p>
</li>
<li>
<p><code>dec</code>: o tamanho total em decimal</p>
</li>
<li>
<p><code>hex</code>: o tamanho total em hexadecimal</p>
</li>
</ul>
<p>Nosso programa por enquanto só tem a seção text, que é exatamente todo o código a partir do rótulo <code>_start</code>.</p>
<blockquote>
<p>Nossa, Leandro, nosso programa tem apenas 12 bytes?</p>
</blockquote>
<p>Aparentemente sim. Vamos confirmar:</p>
<pre><code class="language-bash">$ ls -lh hello

...... 4.6K ... hello
</code></pre>
<blockquote>
<p>Como assim o arquivo tem 4,6Kb? O programa não ocupa 12 bytes apenas?</p>
</blockquote>
<p>Bom, isto ocorre por causa dos headers que são adicionados pelo linker, que contém informação relevante para que o sistema operacional possa admitir a execução do arquivo.</p>
<p>Vamos novamente utilizar o comando <code>size</code> mas desta vez:</p>
<pre><code class="language-bash">$ size --format sysv --radix 16 hello

hello  :
section   size       addr
.text    0xc   0x401000
Total    0xc
</code></pre>
<p>A opção <code>--format</code> indica o formato <code>sysv</code> que traz também os símbolos. E a opção <code>--radix 16</code> permite visualizar o tamanho de cada seção em hexadecimal.</p>
<p>Na seção size, <code>0xc</code> representa o número 12 em decimal. Nada de novo aqui. Mas se repararmos na coluna <code>addr</code>, temos um valor hexadecimal para a seção text (0x401000).</p>
<p>Já vimos isto no artigo anterior, que <code>0x401000</code> se referia ao endereço em hexadecimal de memória virtual que indica o início do programa, lembra?</p>
<p>Hora de confirmar isto com uma análise mais profunda na depuração, chegou o momento de utilizarmos GNU <em>gdb</em>.</p>
<h3>Debugging com GDB</h3>
<p>GDB é um depurador (<em>debugger</em> em inglês) que permite ver o que está acontecendo dentro de um programa <em>em execução</em>.</p>
<p>Com um depurador, podemos analisar as informações estáticas contidas no binário do programa, estabelecer <em>breakpoints</em> (pontos de parada) em qualquer parte do código, executar e analisar mudanças de estado do programa durante sua execução.</p>
<p>Para habilitar o programa com <code>gdb</code>, precisamos montar o programa com a opção <code>-g</code>, que exporta símbolos necessários para depuração:</p>
<pre><code class="language-bash">$ nasm -g -f elf64 hello.asm -o hello.o
$ ld hello.o -o hello
</code></pre>
<p>Podemos verificar os símbolos exportados no binário com o comando size novamente:</p>
<pre><code class="language-bash">$ size --format sysv --radix 16 hello

hello  :
section           size       addr
.text              0xc   0x401000
.debug_aranges    0x30        0x0
.debug_info       0x75        0x0
.debug_abbrev     0x1d        0x0
.debug_line       0x3d        0x0
Total            0x10b

##############

$ ls -lh hello
...... 5.1K ... hello
</code></pre>
<p>Como demonstrado acima, o binário agora contém seções adicionais de "debug" que serão utilizadas pelo <em>gdb</em>, e consequentemente o tamanho do programa teve um acréscimo de <del>500MB</del> 512 bytes!</p>
<p>Sem mais delongas, vamos entrar no <code>gdb</code>:</p>
<pre><code class="language-bash">$ gdb --quiet

(gdb)
</code></pre>
<p>E agora, dentro do shell gdb, podemos utilizar diversos comandos de depuração. O comando <code>help</code> traz a lista de <em>classes de comandos</em> disponíveis:</p>
<pre><code>help

...
aliases -- User-defined aliases of other commands.
breakpoints -- Making program stop at certain points.
data -- Examining data.
files -- Specifying and examining files.
internals -- Maintenance commands.
obscure -- Obscure features.
running -- Running the program.
stack -- Examining the stack.
status -- Status inquiries.
support -- Support facilities.
text-user-interface -- TUI is the GDB text based interface.
tracepoints -- Tracing of program execution without stopping the program.
user-defined -- User-defined commands.
...
</code></pre>
<blockquote>
<p>Para o escopo deste artigo vamos utilizar apenas alguns comandos para depuração, mas a lista de comandos disponíveis é <a href="https://visualgdb.com/gdbreference/commands/">gigante</a>. Deixo o desafio ao leitor para se aventurar com o <code>help</code> do gdb e brincar de depurar qualquer binário executável</p>
</blockquote>
<p>Como queremos depurar o binário <em>hello</em>, podemos carregar os símbolos utilizando o comando <code>file</code>:</p>
<pre><code class="language-bash">(gdb) file hello
Reading symbols from hello...
(gdb)
</code></pre>
<p>O comando <code>info files</code> traz alguns insights:</p>
<pre><code class="language-bash">(gdb) info files
Symbols from "/code/asm-x64/hello".
Local exec file:
        `/code/asm-x64/hello', file type elf64-x86-64.
        Entry point: 0x401000
        0x0000000000401000 - 0x000000000040100c is .text
(gdb)
</code></pre>
<p>Que interessante! O entry point do programa começa justamente em <code>0x401000</code>, que é o que está definido na seção <code>.text</code>.</p>
<p>Para visualizar o código fonte do programa, utilizamos o comando <code>list</code>:</p>
<pre><code class="language-bash">(gdb) list
1       global _start
2
3       _start:
4               mov rdi, 0   ; error code
5               mov rax, 60  ; SYS_exit
6               syscall
(gdb)
</code></pre>
<blockquote>
<p>Lembrando que o programa ainda não está em execução, estamos apenas analisando o binário executável com o gdb</p>
</blockquote>
<p>Com o comando <code>x</code>, de <em>examine</em>, podemos examinar o rótulo <code>_start</code> que é o ponto de entrada do programa:</p>
<pre><code class="language-bash">(gdb) x _start
0x401000 &lt;_start&gt;:      0x000000bf
(gdb)
</code></pre>
<p>Se quisermos executar o programa, podemos fazê-lo com o comando <code>run</code>:</p>
<pre><code class="language-bash">(gdb) run
Starting program: /code/asm-x64/hello
[Inferior 1 (process 7991) exited normally]
(gdb)
</code></pre>
<p>Entretanto, podemos definir breakpoints antes de executar, assim temos controle do estado do programa <strong>em execução</strong>:</p>
<pre><code class="language-bash"># Aqui, definimos um ponto de parada no rótulo _start_
(gdb) break _start
Breakpoint 1 at 0x401000: file hello.asm, line 4.

# Info sobre breakpoints
(gdb) info breakpoints
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000401000 hello.asm:4
(gdb)
</code></pre>
<p>Agora sim, vamos executar:</p>
<pre><code class="language-bash">(gdb) run
Starting program: /code/asm-x64/hello

Breakpoint 1, _start () at hello.asm:4
4               mov rdi, 0   ; error code
(gdb)
</code></pre>
<p>O programa está parado na linha 4 como solicitado. Esta linha no código <strong>não foi avaliada</strong>, pelo que podemos analisar e alterar o estado do programa:</p>
<pre><code class="language-bash"># Neste momento o valor no registrador RDI está 0 (default)
(gdb) info register rdi
rdi            0x0                 0

# Mudamos o valor do registrador para 42
(gdb) set $rdi = 42

# Agora verificamos que foi modificado diretamente do GDB
(gdb) info register rdi
rdi            0x2a                42
(gdb)
</code></pre>
<p>Para avaliar a linha atual, utilizamos o comando <code>next</code>:</p>
<pre><code class="language-bash">(gdb) next
5               mov rax, 60  ; SYS_exit

# E podemos agora verificar que o valor de RDI foi modificado para 0, 
# conforme descrito no programa
(gdb) info register rdi
rdi            0x0                 0
(gdb)
</code></pre>
<p>Poderíamos continuar indo linha a linha com <code>next</code>, ou então continuar a execução do programa com <code>continue</code> que pára no próximo ponto de parada ou executa todas as instruções que faltam até terminar o programa.</p>
<pre><code class="language-bash"># Inicia execução e pára no primeiro breakpoint definido
(gdb) run
Starting program: /Users/leandronsp/Documents/code/asm-x64/hello

Breakpoint 1, _start () at hello.asm:4
4               mov rdi, 0   ; error code

# Continua execução. Neste caso termina o programa pois
# não há mais breakpoints a partir deste ponto
(gdb) continue
Continuing.
[Inferior 1 (process 8000) exited normally]
(gdb)
</code></pre>
<p>Pronto, terminamos a demonstração do primeiro programa com <code>gdb</code>. Para sair, utilizamos o comando <code>exit</code>.</p>
<h3>Rastreando execução com strace</h3>
<p>O utilitário <code>strace</code> permite rastrear todas as chamadas de sistema e sinais que um programa faz. É bastante útil quando queremos saber o que pode ter acontecido com determinada <em>syscall</em>, quais parâmetros foram enviados e o que a syscall retornou.</p>
<pre><code class="language-bash">$ strace ./hello

execve("./hello", ["./hello"], 0x7ffc504b5710 /* 24 vars */) = 0
exit(0)                                 = ?
+++ exited with 0 +++
</code></pre>
<p>Vamos entender a saída do <strong>strace</strong> por partes.</p>
<p><em><em>execve("./hello", ["./hello"], 0x7ffc504b5710 /</em> 24 vars <em>/) = 0:</em></em></p>
<ul>
<li>
<p><code>execve</code> é uma chamada do Linux que executa um determinado programa</p>
</li>
<li>
<p><code>./hello</code> é o caminho para o programa que será executado</p>
</li>
<li>
<p><code>["./hello"]</code> é a lista de argumentos passados para o programa. Como só há o nome do programa (que entra na lista <em>ARGV</em>), indica que este programa não recebe argumentos extras na linha de comando</p>
</li>
<li>
<p><code>0x7ffc504b5710</code> é o endereço de memória onde as variáveis de ambiente do processo em execução estão armazenadas</p>
</li>
<li>
<p><code>/* 24 vars */</code> indica que há 24 variáveis de ambiente definidas no shell atual</p>
</li>
<li>
<p><code>=0</code> é o resultado da chamada <code>execve</code>, o que significa que foi bem-sucedido e executado com sucesso</p>
</li>
</ul>
<p><strong>exit(0) = ?:</strong></p>
<ul>
<li>
<p><code>exit</code> é a chamada de sistema (syscall) feita no sistema operacional, e geralmente é definida no <code>libc</code>, sendo no caso de sistema GNU, <code>glibc</code>. Foi o valor <em>60</em> passado para o registrador RAX, lembra?</p>
</li>
<li>
<p><code>(0)</code> é o parâmetro passado para a função, que neste caso foi o que determinamos no registrador RDI, indicando que nosso programa em execução vai terminar <em>sem erros</em></p>
</li>
<li>
<p><code>= ?</code> indica que o resultado da chamada de sistema não é conhecido, ou seja não houve um retorno <em>explícito</em> de valor da chamada de sistema</p>
</li>
</ul>
<p><strong>+++ exited with 0 +++:</strong></p>
<ul>
<li>
<p><code>+++</code> sinaliza o início de uma mensagem de saída do strace</p>
</li>
<li>
<p><code>exited with 0</code> indica que o programa terminou sem erros</p>
</li>
<li>
<p><code>+++</code> sinaliza o fim da mensagem de saída</p>
</li>
</ul>
<p>Uma vez que entendemos como depurar nosso programa, podemos evolui-lo para imprimir a mensagem "Hello, World" na saída do terminal.</p>
<hr />
<h2>Evoluindo nosso primeiro programa</h2>
<p>Vamos agora evoluir o programa anterior para que possamos imprimir a mensagem "Hello, World" na saída padrão <code>STDOUT</code>.</p>
<p>Para isto, conforme vimos na parte III da saga, "Código de Máquina", vamos por partes.</p>
<h3>Alocando bytes para "Hello, World"</h3>
<p>Precisamos primeiro definir os bytes de cada caracter da string em <em>hexadecimal</em> de acordo com a tabela ASCII, que resulta em <code>48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 0A</code>.</p>
<blockquote>
<p>0x48 para "H", 0x65 para "e", 0x6C para "l" e assim por diante...</p>
</blockquote>
<p>Portanto, se quisermos evoluir o primeiro programa que contém apenas a syscall <code>exit</code>, podemos começar por definir a string utilizando a diretiva <code>db</code> que significa <em>define byte</em>, utilizando o endereço do primeiro byte em um rótulo que iremos chamar de <em>msg</em>:</p>
<pre><code class="language-as">global _start

msg: db 0x48, 0x65, 0x6C, 0x6C, 0x6F, \
		0x2C, 0x20, 0x57, 0x6F, 0x72, \
		0x6C, 0x64, 0xA

_start:
	mov rdi, 0   ; error code
	mov rax, 60  ; SYS_exit
	syscall       
</code></pre>
<p>Antes de sair adicionando mais código, vamos utilizar o <code>gdb</code> para analisar o que esta mudança provoca na memória:</p>
<pre><code class="language-bash"># Examinar o que há no rótulo msg
(gdb) x msg
0x401000 &lt;msg&gt;: 0x6c6c6548

# Examinar o que há no rótulo _start
(gdb) x _start
0x40100d &lt;_start&gt;:      0x000000bf
(gdb)
</code></pre>
<p>Ora ora, o que temos aqui?</p>
<ul>
<li>
<p><code>msg</code> aponta para o endereço <code>0x401000</code> que era o endereço usado pelo <code>_start</code> no nosso programa anterior</p>
</li>
<li>
<p>e agora <code>_start</code> aponta para outro endereço, <code>0x40100d</code> que está <strong>13 bytes</strong> ("d" em hexa) acima de <code>msg</code>, exatamente os 13 bytes da string "Hello, World" adicionado com quebra de linha!!!!!1</p>
</li>
</ul>
<blockquote>
<p>Superb! Mas o que significa o valor <code>0x6c6c6548</code>?</p>
</blockquote>
<p>Se analisarmos com calma, dá pra perceber que se trata dos caracteres da string em ASCII segundo o que foi definido no programa. Mas eles estão <em>invertidos</em>, lembra de endianness que foi explicado no artigo anterior?</p>
<p>Então, esta arquitetura segue o padrão little-endian, onde os bytes são armazenados na ordem inversa, do menos relevante (expoentes menores da base 2) para o mais relevante (expoentes maiores).</p>
<p>Voltando ao gdb, podemos confirmar que todos os bytes da string estão alocados trabalhando com ponteiros de 4 em 4 bytes:</p>
<pre><code class="language-bash">(gdb) x msg
0x401000 &lt;msg&gt;: 0x6c6c6548 ; Hell

(gdb) x msg+4
0x401004:       0x57202c6f ; o, W

(gdb) x msg+8
0x401008:       0x646c726f ; orld
</code></pre>
<p>Ou então, o comando <code>x</code> permite passar uma quantidade junto com o formato de apresentação, por exemplo queremos que traga os primeiros <em>13 hexabytes</em> a partir do ponteiro <strong>msg</strong>:</p>
<pre><code class="language-bash">(gdb) x/13xb msg
0x401000 &lt;msg&gt;: 0x48    0x65    0x6c    0x6c    0x6f    0x2c    0x20    0x57
0x401008:       0x6f    0x72    0x6c    0x64    0x0a
</code></pre>
<p>Exatamente os hexadecimais da string "Hello, World" com quebra de linha!</p>
<p>Mas em Assembly, não precisamos definir os bytes de uma string em hexadecimal. Podemos utilizar os <em>quotes literais</em>, assim o programa fica menos verboso e o assembler faz o processo de traduzir o caracter para o hexadecimal da tabela ASCII:</p>
<pre><code class="language-as">msg: db "Hello, World", 0xA
</code></pre>
<blockquote>
<p>Não conseguimos representar a quebra de linha dentro de quotes literais, então vamos manter esta com 0xA</p>
</blockquote>
<h3>Adicionando a chamada de sistema write</h3>
<p>Como já sabemos, o programa precisa utilizar a syscall <code>write</code> para escrever na saída, que está definida da seguinte forma no <code>glibc</code>:</p>
<pre><code class="language-c">ssize_t write(int fd, const void buf[.count], size_t count);
</code></pre>
<ul>
<li>
<p>nome da syscall vai em RAX</p>
</li>
<li>
<p>primeiro argumento (file descriptor, no caso o <em>STDOUT</em>) vai em RDI</p>
</li>
<li>
<p>segundo argumento (ponteiro para o início do buffer) vai em RSI</p>
</li>
<li>
<p>terceiro argumento (quantidade de bytes a serem escritos) vai em RDX</p>
</li>
</ul>
<pre><code class="language-as">global _start

msg: db "Hello, World", 0xA
_start:
	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	; Chamada de sistema
	; glibc -&gt; ssize_t write(int fd, 
							 const void buf[.count], 
							 size_t count)
	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	mov rdi, 1   ; STDOUT
	mov rsi, msg ; ponteiro para o início da string
	mov rdx, 13  ; quantidade de bytes a serem escritos
	mov rax, 1   ; nome da syscall: SYS_write
	syscall      ; chamada de sistema

	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	; Chamada de sistema
	; glibc -&gt; void _exit(int status)
	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	mov rdi, 0   ; erro de saída
	mov rax, 60  ; nome da syscall: SYS_exit
	syscall           
</code></pre>
<p>Ao compilar o programa com <code>nasm + ld</code>, seguindo a mesma lógica do primeiro programa, temos de fato a saída tão desejada:</p>
<pre><code>$ ./hello
Hello, World
</code></pre>
<p><em>Yay! que dia maravilhoso!</em></p>
<p>Vamos ver como fica o trace disso tudo agora?</p>
<pre><code class="language-bash">$ strace ./hello

execve("./hello", ["./hello"], 0x7fff139437f0 /* 24 vars */) = 0
write(1, "Hello, World\n", 13Hello, World
)          = 13
exit(0)                                 = ?
+++ exited with 0 +++
</code></pre>
<p>Wow, podemos ver que, agora, o programa executa primeiro a syscall <code>write</code>, que retorna o valor <code>13</code>, que é quantidade de bytes escritos <em>com sucesso</em>; e a seguir executa a syscall <code>exit</code>, também com sucesso, indicando que nosso programa imprime a string na saída e termina sem erros.</p>
<p>Como ficou o tamanho do programa agora?</p>
<pre><code class="language-bash">$ size hello

   text    data     bss     dec     hex filename
     52       0       0      52      34 hello
</code></pre>
<p>Hmm, parece que a seção <code>text</code> aumentou de tamanho, que é a adição da string "Hello, World" e das instruções para a syscall <code>write</code>. Mas por enquanto é a única seção existente:</p>
<pre><code class="language-bash">$ size --format sysv --radix 16 hello

hello  :
section           size       addr
.text             0x34   0x401000
(omitindo seções de debug)
Total            0x139

</code></pre>
<p>Podemos ver que a string definida no rótulo <code>msg</code>, que começa no endereço <code>0x401000</code> está contida na seção <code>.text</code>.</p>
<blockquote>
<p>Isto é um problema?</p>
</blockquote>
<p>Mais ou menos:</p>
<ol>
<li>O rótulo <code>msg</code> , que é um "dado", contendo a string, está definido num endereço de memória <strong>anterior</strong>, ou seja, em endereço de memória mais baixo em direção a 0</li>
<li>O rótulo <code>_start</code>, que é o início do programa, está definido num endereço <strong>posterior</strong>, ou seja, em endereço de memória mais alto com relação à string</li>
</ol>
<p>No sistema operacional, todo programa é encapsulado em um processo tal como vimos no artigo anterior. E sendo um processo, é submetido a um "layout" que deve seguir algumas regras.</p>
<h3>Layout de memória</h3>
<p>Fazendo paralelo com a saída do comando <code>size</code>, a memória do programa segue um layout, que basicamente contém as seguintes seções, ou <em>segmentos</em> de memória:</p>
<ul>
<li>
<p>text</p>
</li>
<li>
<p>data</p>
</li>
<li>
<p>bss</p>
</li>
</ul>
<p>Já falamos disto anteriormente neste artigo, mas basicamente na seção <code>text</code> fica todo o código, instruções do programa.</p>
<p>Na seção <code>data</code>, ficam dados inicializados (aqui deveria estar a nossa string). E na seção <code>bss</code> vão os dados não-inicializados, mas já com uma área pré-alocada na memória.</p>
<p>Em termos de <strong>espaço virtual de memória</strong> do programa, a seção <code>text</code> deve ficar nos endereços de memória mais baixos, próximos ao entry point <code>0x401000</code>.</p>
<p>Com isto, o programa deve crescer a partir da seção <code>text</code> em direção a <code>data</code> e <code>bss</code>, dos menores endereços de memória para os maiores (da esquerda pra direita):</p>
<pre><code>text -&gt; data -&gt; bss
</code></pre>
<p>Ou então, analisando numa imagem em vertical, de baixo pra cima:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yy0iqqbaz3ox378srcw6.png" alt="layout de memoria 2" /></p>
<p>Existem mais seções no layout mas vamos adicioná-las à medida que avançamos no artigo. Por agora, como nosso programa está tratando dados (<code>msg</code>) como <code>text</code>, devemos colocar na seção correta, que é <code>data</code>:</p>
<pre><code class="language-as">global _start

; segmento de dados (endereços mais altos)
section .data
msg: db "Hello, World", 0xA

; segmento de texto (endereços mais baixos)
section .text
_start:
	mov rdi, 1   ; STDOUT
	mov rsi, msg ; ponteiro para o início da string
	mov rdx, 13  ; quantidade de bytes a serem escritos
	mov rax, 1   ; nome da syscall: SYS_write
	syscall      ; chamada de sistema

	mov rdi, 0   ; erro de saída
	mov rax, 60  ; nome da syscall: SYS_exit
	syscall  
</code></pre>
<p>Com <code>gdb</code>, podemos conferir que agora estamos obedecendo o layout de memória estabelecido para o programa:</p>
<pre><code class="language-bash">(gdb) x _start
0x401000 &lt;_start&gt;:      0x000000bf

(gdb) x &amp;msg
0x402000 &lt;msg&gt;: 0x6c6c6548
(gdb)
</code></pre>
<blockquote>
<p>Note que para acessar <code>msg</code> no segmento de dados, precisamos examinar através da <em>referência</em>, com o operador <code>&amp;</code></p>
</blockquote>
<h3>Definindo constantes</h3>
<p>Em Assembly podemos definir constantes que podem ser reutilizadas em diversas partes do programa, evitando assim alguma redundância com repetição de código e valores.</p>
<p>A diretiva <code>%define</code> permite definir valores constantes tanto para string quanto números:</p>
<pre><code class="language-as">global _start

%define SYS_write 1
%define SYS_exit 60
%define EXIT_STATUS 1
%define STDOUT 1
%define NEWLINE 0xA

section .data
msg: db "Hello, World", NEWLINE

section .text
_start:
	mov rdi, STDOUT
	mov rsi, msg 
	mov rdx, 13
	mov rax, SYS_write
	syscall      

	mov rdi, EXIT_STATUS
	mov rax, SYS_exit
	syscall  
</code></pre>
<p>Podemos também definir uma constante baseada em uma expressão aritmética. Por exemplo, ao invés de deixarmos o tamanho em bytes com valor fixo <em>13</em>, podemos fazer que isto seja calculado com base em aritmética de ponteiros na memória com a diretiva <code>equ</code>:</p>
<pre><code class="language-as">...
section .data
msg: db "Hello, World", NEWLINE
msgLen: equ $ - msg
...
</code></pre>
<p>O operador <code>$</code> tem o ponteiro de memória para o último byte no programa, no caso o <code>NEWLINE</code> definido na linha anterior. Ao subtrair do ponteiro <code>msg</code> com a expressão <code>$ - msg</code>, temos o tamanho em bytes calculado e desta forma não precisa ser um valor fixo em RDX:</p>
<pre><code class="language-as">global _start

%define SYS_write 1
%define SYS_exit 60
%define EXIT_STATUS 1
%define STDOUT 1
%define NEWLINE 0xA

section .data
msg: db "Hello, World", NEWLINE
msgLen: equ $ - msg

section .text
_start:
	mov rdi, STDOUT
	mov rsi, msg 
	mov rdx, msgLen
	mov rax, SYS_write
	syscall      

	mov rdi, EXIT_STATUS
	mov rax, SYS_exit
	syscall  
</code></pre>
<p><em>Wonderful! Nosso programa agora tá muito mais elegante!</em></p>
<p>Ufa, parece que terminamos o nosso primeiro programa e este por si só já foi uma jornada longa. Mas tenha um pouco mais de paciência, <strong>vem comigo</strong>, pois chegou o momento de escrevermos um programa um pouco mais sofisticado.</p>
<p>Hora de explorar mais funcionalidades no Assembly e entrar no mundo da <em>stack</em>.</p>
<hr />
<h2>Um programa mais sofisticado</h2>
<p>Vamos começar por um programa simples e evoluindo conforme depuramos e entendemos a memória. Ao fim, o programa deve ser capaz de receber um nome através dos argumentos da linha de comando e imprimir "Hi, <code>&lt;nome&gt;</code>".</p>
<p>Desejado:</p>
<pre><code class="language-bash">$ ./greeting Leandro
Hi, Leandro
</code></pre>
<h3>Definindo labels</h3>
<p>Já sabemos que o programa precisa imprimir "Hi, " alguma coisa. Então as instruções pra syscall <code>write</code> são necessárias, e já fazendo uso de constantes:</p>
<pre><code class="language-as">global _start

%define SYS_write 1
%define SYS_exit 60
%define STDOUT 1

section .data
greet: db "Hi", 0xA

section .text
_start:
	mov rdi, STDOUT
	mov rsi, greet
	mov rdx, 3
	mov rax, SYS_write
	syscall
	
	mov rdi, 0
	mov rax, SYS_exit
	syscall
</code></pre>
<p>Este programa imprime "Hi" apenas. Mas podemos melhorar a organização separando em blocos com algum valor <em>semântico</em>:</p>
<ul>
<li>
<p>separar o bloco de <code>exit</code></p>
</li>
<li>
<p>separar o bloco de <code>write</code></p>
</li>
</ul>
<p>Assembly emprega o conceito de <strong>labels</strong>, que são rótulos, mas que podem ser definidas em qualquer parte do código. Utilizando o caracter <em>ponto</em> (<code>.</code>), o programa fica bem mais expressivo:</p>
<pre><code class="language-as">global _start

%define SYS_write 1
%define SYS_exit 60
%define STDOUT 1

section .data
greet: db "Hi", 0xA

section .text
_start:
.print:
	mov rdi, STDOUT
	mov rsi, greet
	mov rdx, 3
	mov rax, SYS_write
	syscall
.exit:
	mov rdi, 0
	mov rax, SYS_exit
	syscall
</code></pre>
<p>Assim como qualquer rótulo, o programa vai executando top-down. O que fizemos aqui foi apenas colocar rótulos em determinadas partes do programa, mas sem <em>alterar seu fluxo de execução</em>.</p>
<h3>Desvio de fluxo com jump</h3>
<p>Se quisermos alterar o fluxo de execução, podemos utilizar a instrução <code>JMP</code> que altera o fluxo do programa para outro ponto, continuando <em>a partir desde novo ponto</em>.</p>
<pre><code class="language-as">global _start

%define SYS_write 1
%define SYS_exit 60
%define STDOUT 1

section .data
greet: db "Hi", 0xA

section .text
_start:
	; Faz o jump para a label .print, sem passar por .exit
	jmp .print
.exit:
	mov rdi, 0
	mov rax, SYS_exit
	syscall
.print:
	mov rdi, STDOUT
	mov rsi, greet
	mov rdx, 3
	mov rax, SYS_write
	syscall
	
	; Faz o jump para a label .exit, caso contrário o programa não terminaria
	; da forma adequada (todo programa deve terminar)
	jmp .exit
</code></pre>
<p>Este foi um exemplo bastante simples com jump e desvio de fluxo. Mas é possível também desviar o fluxo, executar a lógica do novo fluxo, e <em>retornar</em> ao ponto anterior.</p>
<p>Entretanto, para que isto funcione, vamos imaginar uma possível solução:</p>
<ul>
<li>
<p>definir algum registrador "especial" que guarda sempre o ponteiro da próxima instrução</p>
</li>
<li>
<p>antes de desviar o fluxo, guardar o endereço de memória da próxima instrução do programa em alguma <em>estrutura de dados</em> para que possa ser resgatado quando a lógica do desvio terminar</p>
</li>
</ul>
<p>Sim, estamos falando do desvio com <code>call</code>, <code>ret</code>, registradores e pilha.</p>
<h3>Desvio de fluxo com call</h3>
<p>Tendo o exemplo anterior, ao invés de fazer <code>jmp</code>, vamos utilizar a instrução <code>call</code> que faz o desvio para outra rotina:</p>
<pre><code class="language-as">call .print  ; &lt;------ chamada da rotina
</code></pre>
<p>Além disso, a última linha da rotina <code>.print</code> deve "retornar" o fluxo desviado para o ponto anterior.</p>
<pre><code class="language-as">.print:
	mov rdi, STDOUT
	mov rsi, greet
	mov rdx, 3
	mov rax, SYS_write
	syscall
	ret  ; &lt;------ retorno da rotina
</code></pre>
<p>Antes de analisarmos com <code>gdb</code> passo a passo, precisamos entender um aspecto importante dos programas no sistema operacional.</p>
<p>Quando um programa é executado, ele é definido em uma estrutura chamada <strong>processo</strong> (já falamos disto no artigo anterior). Todo processo carrega o layout de memória definido no binário do programa, conforme vimos anteriormente:</p>
<pre><code>text -&gt; data -&gt; bss
</code></pre>
<p>Nos endereços mais <em>altos</em> da memória virtual do processo (programa em execução), o sistema operacional também define uma outra estrutura de dados, chamada <strong>stack</strong>, que tem um formato de pilha (LIFO, Last In, First Out).</p>
<pre><code>text -&gt; data -&gt; bss ---------&gt; &lt;-------- stack
</code></pre>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z7lyluh9a4hqkzl3yo7s.png" alt="layout de memória com stack" /></p>
<p>A stack fica nos endereços mais altos e carrega informações como argumentos do programa, lista de variáveis de ambiente definidas no <em>shell</em>, argumentos para funções entre qualquer informação pertinente para o programa. Stack sempre cresce <em>para baixo</em> em direção aos endereços menores.</p>
<p><strong>rsp</strong>
Em um programa Assembly x86, é preciso armazenar o ponteiro atual do topo da stack, e esta informação fica no registrador RSP, ou <em>stack pointer</em>.</p>
<p><strong>rip</strong>
Já o ponteiro da instrução atual fica no registrador RIP, ou <em>instruction pointer</em>.</p>
<p>Com estes dois registradores conseguimos demonstrar o uso de call e ret para desvio de fluxo. Voltando ao programa:</p>
<pre><code class="language-as">global _start

%define SYS_write 1
%define SYS_exit 60
%define STDOUT 1

section .data
greet: db "Hi", 0xA

section .text
_start:
	call .print     ; &lt;--------- desvio do fluxo
	
	; aqui neste ponto, já continua a execução normal em direção ao exit
	; para terminar o programa
.exit:
	mov rdi, 0
	mov rax, SYS_exit
	syscall
.print:
	mov rdi, STDOUT
	mov rsi, greet
	mov rdx, 3
	mov rax, SYS_write
	syscall
	ret            ; &lt;---------- retorno ao ponto anterior
</code></pre>
<p>E agora demonstrando com <code>gdb</code>:</p>
<pre><code class="language-bash">$ gdb greeting

# Breakpoint em _start, início do programa
(gdb) break _start_
(gdb) run

# RIP apontando para 0x401000 (_start), o entry point do programa
(gdb) info register $rip
rip            0x401000            0x401000 &lt;_start&gt;

# RSP apontando para um endereço de memória
# Se formos examinar com x $rsp, temos
# 0x7fffffffe450: 0x00000001, que é a quantidade de argumentos
# passados, no caso 1 representa apenas o nome do programa, ou seja
# não há argumentos na linha de comando
(gdb) i r $rsp
rsp            0x7fffffffe450      0x7fffffffe450
</code></pre>
<p>Até aqui okay, agora vamos andar um <code>step</code> para que o desvio de <code>call</code> seja avaliado e analisar o RIP:</p>
<pre><code class="language-bash">(gdb) step
18              mov rdi, STDOUT

# O RIP andou conforme esperado
(gdb) i r $rip
rip            0x401011            0x401011 &lt;_start.print&gt;

# RIP apontando para 0x000001bf, que é BF 01 00 00 
# Lembra? é o opcode pra MOV RDI, 1
# Exatamente onde paramos
(gdb) x $rip
0x401011 &lt;_start.print&gt;:        0x000001bf
</code></pre>
<p>E a stack (RSP) como ficou?</p>
<pre><code class="language-bash"># A pilha andou alguns bytes (no caso foi feito um "push", o que a fez crescer para endereços de memórias menores)
# Lembra? Pilha "cresce pra baixo" na memória
(gdb) i r rsp
rsp            0x7fffffffe448      0x7fffffffe448

# Opaaa, o que temos aqui? 0x00401005
# É alguma pista...
(gdb) x $rsp
0x7fffffffe448: 0x00401005

# Examinando o ponteiro do início do programa...
(gdb) x _start
0x401000 &lt;_start&gt;:      0x00000ce8

# Se andarmos alguns bytes, 
# temos exatamente o endereço da label .exit, 0x401005
(gdb) x _start + 5
0x401005 &lt;_start.exit&gt;: 0x000000bf
</code></pre>
<p>Se você prestou atenção nos comentários do snippet acima...</p>
<blockquote>
<p>É muito importante prestar atenção em todos os comentários, se não estiver fazendo isso, volte o artigo do início e tente acompanhar <em>no terminal</em>, é extremamente importante para entender os conceitos</p>
</blockquote>
<p>...se prestarmos a devida atenção, este é o endereço que tá no topo da pilha agora, que foi adicionado pela instrução <code>call</code>.</p>
<blockquote>
<p>Ok Leandro, mas como fazemos então para voltar ao ponto anterior?</p>
</blockquote>
<p>Calma, jovem. Estamos parados no início da rotina <code>.print</code>. Vamos continuar a depuração com <code>gdb</code> até parar em <code>ret</code>:</p>
<pre><code class="language-bash">(gdb) next
19              mov rsi, greet
(gdb) next
20              mov rdx, 3
(gdb) next
21              mov rax, SYS_write
(gdb) next
22              syscall
(gdb) next
Hi
_start.print () at greeting.asm:23
23              ret
(gdb)
</code></pre>
<p>Nice, antes de avaliar a instrução <code>ret</code>, podemos ver que RIP andou mas RSP continua na mesma, com o endereço da próxima instrução antes do desvio:</p>
<pre><code class="language-bash"># RIP aponta para a instrução da linha "ret"
(gdb) x $rip
0x40102c &lt;_start.print+27&gt;:     0x000000c3

# RSP aponta para o endereço de memória que está a instrução .exit, que
# vem a seguir ao desvio feito com "call" lá em cima
(gdb) x $rsp
0x7fffffffe448: 0x00401005
</code></pre>
<p>Vamos andar com <code>ret</code> e....</p>
<pre><code class="language-bash"># RIP agora aponta para 0x401005, que é a instrução .exit
(gdb) x $rip
0x401005 &lt;_start.exit&gt;: 0x000000bf

# Foi feito "pop" em RSP e agora este aponta para o topo da pilha
# com o valor exato quando estava no início do programa
(gdb) x $rsp
0x7fffffffe450: 0x00000001
(gdb)
</code></pre>
<p><em>OMG!! Acabamos de demonstrar manipulação de registradores e pilhas</em>.</p>
<h3>Brincando com pilhas</h3>
<p><em>Pilhas é divertido.</em></p>
<blockquote>
<p>Mas prefiro filas, gosto de tratar as coisas de modo ordenado. Quem chega primeiro precisa ser atendido primeiro kkkkkkkk</p>
</blockquote>
<p>Mas com pilhas não é assim. Quem entra por último sai primeiro.</p>
<p>Com base nisto, como podemos manipular a stack do programa? Vamos alterar um pouco o código adicionando o ponteiro de <code>greet</code> na stack:</p>
<pre><code class="language-as">global _start

%define SYS_write 1
%define SYS_exit 60
%define STDOUT 1

section .data
greet: db "Hi", 0xA

section .text
_start:
	push greet    ; &lt;----- adiciona o ponteiro de greet na stack
	call .print
.exit:
	mov rdi, 0
	mov rax, SYS_exit
	syscall
.print:
	mov rdi, STDOUT
	mov rsi, greet
	mov rdx, 3
	mov rax, SYS_write
	syscall
	ret
</code></pre>
<p>No <code>gdb</code>, vamos colocar um breakpoint na linha da chamada <code>call</code>:</p>
<pre><code class="language-bash"># Breakpoint na linha 13
(gdb) b 13
Breakpoint 1 at 0x401005: file greeting.asm, line 13.

# Run
(gdb) r

# Examinando o topo da pilha
(gdb) x $rsp
0x7fffffffe448: 0x00402000

# Examinando o endereço de memória que tá no topo da pilha
(gdb) x 0x00402000
0x402000 &lt;greet&gt;:       0x2c0a6948
</code></pre>
<p><em>Cool</em>, temos <code>0x48 0x69 0x0A</code> (little-endian), exatamente a string "Hi" seguida de uma quebra de linha. Com esta rica informação, ao invés da rotina <code>.print</code> passar pro registrador RSI o ponteiro de <code>greet</code>, porque não passar o ponteiro do topo da pilha?</p>
<p>Algo nessa linha:</p>
<pre><code class="language-as">; ao invés disso (atual)
mov rsi, greet

; que tal mover o ponteiro que tá em rsp (topo da pilha) para rsi
mov rsi, rsp
</code></pre>
<p>Por enquanto, seguramos esta ideia no bolso. Ainda no <code>gdb</code>, vamos continuar analisando a pilha depois de entrar na rotina:</p>
<pre><code class="language-bash">(gdb) step
_start.print () at greeting.asm:19

# Agora o topo da pilha foi modificado, "call" colocou o endereço de 
# memória da próxima instrução quando voltar do "ret"
(gdb) x $rsp
0x7fffffffe440: 0x0040100a

# O endereço de memória aponta justamente pra próxima instrução quando voltar do "ret", no caso a instrução que tá na label .exit do programa
(gdb) x 0x0040100a
0x40100a &lt;_start.exit&gt;: 0x000000bf
</code></pre>
<p>Mas agora o topo da pilha estraga nossa ideia de fazer <code>mov rsi, rsp</code>, mas podemos fazer aritmética com ponteiros e mover o conteúdo resultante, e é muito fácil:</p>
<pre><code class="language-bash"># Topo da pilha apontando pra instrução guardada pelo "call"
(gdb) x $rsp
0x7fffffffe440: 0x0040100a

# Topo da pilha + 8 bytes apontando pro endereço onde tá a string "Hi"
(gdb) x $rsp+8
0x7fffffffe448: 0x00402000
</code></pre>
<blockquote>
<p>Nesta arquitetura, a pilha, assim como os registradores, armazenam por padrão até 8 bytes por cada informação</p>
</blockquote>
<p>Então teoricamente, tudo o que precisamos é <code>mov rsi, [rsp + 8]</code></p>
<blockquote>
<p>Note que é preciso usar <code>[rsp + 8]</code>, com square brackets é uma forma de fazermos aritmética de ponteiros e acessar o valor resultante da operação na memória, no caso o endereço apontando para a string "Hi"</p>
</blockquote>
<p>Para finalizar este primeiro exemplo, é muito importante fazermos "pop" da pilha. Todo <code>push</code> deve ter um <code>pop</code>, caso contrário podemos gastar a pilha desnecessariamente e talvez chegar a um stack overflow se exagerarmos bastante.</p>
<pre><code class="language-as">global _start

%define SYS_write 1
%define SYS_exit 60
%define STDOUT 1

section .data
greet: db "Hi", 0xA

section .text
_start:
	push greet             ; &lt;----- push na pilha
	call .print
	pop rbp                ; &lt;----- pop da pilha, jogando o valor em rbp
	                       ; note que rbp é outro registrador de propósito geral,
	                       ; mas que é utilizado para manter a base da pilha
.exit:
	mov rdi, 0
	mov rax, SYS_exit
	syscall
.print:
	mov rdi, STDOUT
	mov rsi, [rsp + 8]     ; &lt;----- 8 bytes depois do topo da pilha está o
	                       ; endereço de memória da string
	mov rdx, 3
	mov rax, SYS_write
	syscall
	ret
</code></pre>
<p>Podemos reparar 2 coisas:</p>
<ul>
<li>
<p>A rotina <code>.print</code> está ficando bastante genérica, ou seja ela não sabe o que está na pilha, simplesmente move para o registrador RSI e faz a syscall <code>write</code></p>
</li>
<li>
<p>A rotina  <code>.print</code> ainda usa o tamanho em bytes como valor fixo, no caso 3 bytes. Deveria ser dinâmico também se quisermos fazer com que esta rotina seja bem genérica</p>
</li>
</ul>
<p>Colocamos o tamanho também na pilha? <em>Nah</em>, seria mais interessante ainda se calculássemos dinamicamente o que vem da pilha. Para fazer este cálculo, teríamos que "iterar", em forma de <em>loop</em>, por cada byte que queremos imprimir, incrementar em um registrador e utilizar isto na syscall.</p>
<p>Vamos entrar no mundo dos <strong>loops</strong> e <strong>condicionais</strong>.</p>
<h3>Calculando tamanho dinamicamente com loop</h3>
<p>Combinando labels e jumps, podemos criar um <em>loop</em> em assembly, como neste pequeno exemplo a seguir:</p>
<pre><code class="language-as">; Um loop infinito sem condição de parada
; Não façam isso

global _start

_start:
.loop
	jmp .loop
</code></pre>
<p>Entretanto para adicionarmos uma <em>condição de parada</em> do loop, é necessário utilizar uma instrução de <strong>comparação</strong> e outra que <strong>muda algum estado</strong>.</p>
<p>No nosso exemplo, vamos introduzir um <em>loop</em> que calcula o tamanho da string <em>antes de fazer a syscall</em>. Entendendo a necessidade:</p>
<pre><code class="language-as">; Pseudo-code

.print:
	mov rdi, STDOUT
	mov rsi, [rsp + 8]   ; string em RSI     
	                       
	mov rdx, ?           ; &lt;--- aqui devemos introduzir um loop que vai
	                     ; modificando o valor de RDX, lendo byte a byte
	                     ; o conteúdo da string
	mov rax, SYS_write
	syscall
	ret
</code></pre>
<p>Para resolver isto, podemos criar uma <strong>label</strong> chamada <code>.calculate_size</code> que contém um <code>jmp</code> para ela mesma:</p>
<pre><code class="language-as">.print:
	mov rsi, [rsp + 8]       ; string em RSI 
	mov rdx, 0               ; RDX começa em 0
.calculate_size:             ; label
	jmp .calculate_size      ; jmp "recursivo"
.done:
	mov rdi, STDOUT
	mov rax, SYS_write
	syscall
	ret
</code></pre>
<p>Ao rodarmos o programa, <em>obviamente</em> caímos em loop infinito. Precisamos definir uma condição de parada, que consiste em:</p>
<ul>
<li>
<p>mudar o estado de alguma variável condicional</p>
</li>
<li>
<p>desviar o fluxo para outra <em>label</em> quando a condição for verdadeira</p>
</li>
</ul>
<p>Em Assembly, podemos fazer a mudança de estado utilizando a instrução <code>inc</code>:</p>
<pre><code class="language-as">.print:
	mov rsi, [rsp + 8]     
	mov rdx, 0             ; RDX (contador) começa em 0
.calculate_size:
	inc rdx                ; incrementa o valor que está em RDX (linha 23)
	jmp .calculate_size
.done:
	mov rdi, STDOUT
	mov rax, SYS_write
	syscall
	ret
</code></pre>
<p>Com gdb, verificamos que o valor de RDX está sempre sendo incrementado:</p>
<pre><code class="language-bash"># Adicionar breakpoint na linha 23 (&lt;&lt;inc rdx&gt;&gt;)
(gdb) break 23
Breakpoint 1 at 0x401021: file live.asm, line 23.

# Executar o programa, que vai no primeiro breakpoint
(gdb) run
Breakpoint 1, _start.calculate_size () at live.asm:23
23              inc rdx

# Continuar execução até o próximo breakpoint ou fim do programa.
# Mas como estamos em loop, o programa vai parar de novo nesta linha
(gdb) continue

# Atalho para "info register rdx"
(gdb) i r rdx
rdx            0x1                 1

# Próxima iteração...
(gdb) continue
(gdb) i r rdx
rdx            0x2                 2

# E assim infinitamente pois não temos ainda a segunda premissa da condição de parada, que é a condicional
(gdb) continue
(gdb) i r rdx
rdx            0x19                25
</code></pre>
<p>Como podemos elaborar esta condicional, uma vez que o valor em RDX pode ser infinito, logo ter <em>todas as possibilidades</em>?</p>
<p>Uma ideia é irmos <strong>consumindo</strong> byte a byte da string até chegar a <em>zero</em>. Para isto, podemos definir o fim da string com <code>0x0</code> e fazer <em>aritmética binária</em> na própria string, consumindo os bytes até chegar a <code>0x0</code>!</p>
<p>Eis o exemplo com um pseudo-código:</p>
<pre><code class="language-as">; "Hi", 0 
; que em hexabyte fica 0x49, 0x69, 0x00

INCREMENT
0x69 0x00   ; consumiu o byte mais à esquerda 0x49

INCREMENT   ; consumiu o byte mais à esquerda 0x69
0x00 0x...
</code></pre>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3v83k677xw88fg49dn86.png" alt="increment em endereço de memória" /></p>
<blockquote>
<p>Nossa Leandro, que fantástico! Podemos então fazer <code>inc</code> em um registrador que contém a string, nesse caso o próprio RSI?</p>
</blockquote>
<p><em>Isso mesmo!</em></p>
<pre><code class="language-as">.....
section .data
greet: db "Hi", 0xA, 0      ; &lt;--- adicionamos o "zero" para identificar o 
                            ; fim da string

.....

.print:
	mov rsi, [rsp + 8]     
	mov rdx, 0
.calculate_size:
	inc rdx                 ; incrementa o valor inteiro em RDX (contador)
	inc rsi                 ; &lt;--- além de incrementar o RDX, incrementamos
	                        ; também o RSI, que contém o endereço de
	                        ; memória para a string. Aritmética em hexabytes
	                        ; vai fazer o efeito de "consumir os bytes até zero
	jmp .calculate_size
.done:
	mov rdi, STDOUT
	mov rax, SYS_write
	syscall
	ret

</code></pre>
<p>Agora, com <code>gdb</code>, vamos verificar o que está acontecendo com nosso programa:</p>
<pre><code class="language-bash"># Breakpoint na linha &lt;jmp .calculate_size&gt;
(gdb) break 25
Breakpoint 1 at 0x401027: file live.asm, line 25.

(gdb) run

# Cool, o contador RDX foi incrementado
(gdb) i r rdx
rdx            0x1                 1

# Em RSI, temos outro endereço de memória.
# Anteriormente era o início da string, 0x402000, mas agora está
# apontando para 0x402001
(gdb) i r rsi
rsi            0x402001            4202497

# Wow! Temos os bytes da string "i", seguido de "\n", e depois o 0x00
# Parece que o inc RSI funcionou como esperávamos?
(gdb)x /4xb 0x402001
0x402001:       0x69    0x0a    0x00    0x2c

# A vida continua...
(gdb) continue

# Caminho mais curto
# E parece que RSI andou mais ainda, agora apontando para o byte "\n"
(gdb) x $rsi
0x402002:       0x0a

# A vida continua...
(gdb) continue

# O nosso grande momento! Agora RSI aponta para 0x00
(gdb) x $rsi
0x402003:       0x00
</code></pre>
<p>Tudo o que precisamos fazer, neste momento, é comparar <strong>o valor que está em RSI</strong> com <em>zero</em>. Se chegou a zero, significa que podemos <em>parar o loop</em>. Vamos verificar o que está no contador RDX (esperamos que seja 3):</p>
<pre><code class="language-bash">(gdb) i r rdx
rdx            0x3                 3
</code></pre>
<p><em>Yay! Que grande momento!</em></p>
<p>Mas como verificar em Assembly se chegou ou não no valor? Existe "IF" e "ELSE" em Assembly?</p>
<blockquote>
<p>Hell no!</p>
</blockquote>
<p>Não. Não tem "IF" e "ELSE" em Assembly.</p>
<p>Uma possível solução seria:</p>
<ul>
<li>
<p>utilizar uma instrução que compare o valor de um registrador ou em algum endereço de memória com <strong>qualquer outro valor</strong></p>
</li>
<li>
<p>esta instrução iria guardar o resultado da comparação em outro registrador "especial"</p>
</li>
<li>
<p>utilizar outra instrução para fazer <em>desvio do fluxo</em> de acordo com o valor que estive neste registrador especial</p>
</li>
</ul>
<p>Sim, é aí que entramos no tal do registrador <strong>RFLAGS</strong>.</p>
<h3>RFLAGS</h3>
<p>O registrador de <em>flags</em> é um registrador de status que mantém sempre o estado atual da CPU, neste caso estamos referindo a uma CPU x86_64, pelo que chamamos este registrador de <strong>RFLAGS</strong>.</p>
<p>Este registrador guarda <em>opcodes condicionais</em>, que são resultado de diversas operações lógicas e aritméticas que afetam o estado da CPU.</p>
<p>Voltando ao nosso exemplo, podemos comparar o registrador RSI com o valor 0, e então verificar o que está acontecendo com o registrador <code>eflags</code>:</p>
<pre><code class="language-as">.print:
	mov rsi, [rsp + 8]     
	mov rdx, 0
.calculate_size:
	inc rdx
	inc rsi
	cmp byte [rsi], 0x00   ; &lt;-- aqui comparamos (em byte) o valor que está
	                       ; em RSI com o byte 0x00
	jmp .calculate_size
.done:
	mov rdi, STDOUT
	mov rax, SYS_write
	syscall
	ret
</code></pre>
<p>E com isto podemos conferir com <code>gdb</code>:</p>
<pre><code class="language-bash"># Breakpoint na linha &lt;cmp byte [rsi], 0x00&gt;
(gdb) break 25

(gdb) run

# O que temos no primeiro byte de RSI? "i", pois o "H" já foi
# consumido no &lt;inc rsi&gt;
(gdb) x /1xb $rsi
0x402002:       0x69

# E no eflags?
# Nossa, temos o IF que estávamos precisando!!!!!11
(gdb) i r eflags
eflags         0x202               [ IF ]
</code></pre>
<blockquote>
<p>Calma jovem, <code>IF</code> não é o que você está pensando!</p>
</blockquote>
<p><code>IF</code> é uma flag chamada <em>interrupt flag</em>, que está sempre presente no programa em execução. Ela determina se o programa pode ou não sofrer interrupções de hardware. No nosso caso, está sempre habilitada por padrão, e é por este motivo que podemos fazer chamadas de sistema (syscalls).</p>
<p>Continuando no gdb...</p>
<pre><code class="language-bash">(gdb) continue

# O que temos em RSI? "\n"
(gdb) x /1xb $rsi
0x402002:       0x0a

# Executar a instrução &lt;cmp byte [rsi], 0x00&gt;
(gdb) next

# Ok, segunda iteração continua na mesma, sem flags adicionais
(gdb) i r eflags
eflags         0x202               [ IF ]

######## Próxima iteração ##########

(gdb) continue

# O que temos em RSI? 0x00, cool.
(gdb) x /1xb $rsi
0x402003:       0x00

# Executar a instrução &lt;cmp byte [rsi], 0x00&gt;
(gdb) next

# Outras flags foram adicionadas ao estado: PF e ZF
(gdb) i r eflags
eflags         0x246               [ PF ZF IF ]
</code></pre>
<p><code>PF</code> é a <em>parity flag</em>, que é adicionada quando uma operação aritmética em qualquer registrador resulta em paridade ímpar.</p>
<blockquote>
<p>Não é do escopo deste artigo entrar em detalhes sobre PF, sugiro a leitura <a href="https://en.wikipedia.org/wiki/Parity_flag">sobre o assunto</a></p>
</blockquote>
<p>Já a <code>ZF</code> é chamada <strong>zero flag</strong>, adicionada quando uma operação aritmética resulta em zero, que é exatamente o que estamos buscando aqui.</p>
<p>Agora o que precisamos é desviar o fluxo (lembra do <code>jmp</code>) quando a <em>flag zero está presente</em>. Para isto, temos a disposição diversas instruções de jump baseadas em flags:</p>
<ul>
<li>
<p>jz (jump if zero)</p>
</li>
<li>
<p>jnz(jump if not zero)</p>
</li>
<li>
<p>je (jump if equal)</p>
</li>
<li>
<p>jne (jump if not equal)</p>
</li>
</ul>
<blockquote>
<p>Isto pra mencionar apenas algumas, existem muitas outras que podem ser consultadas <a href="https://en.wikibooks.org/wiki/X86_Assembly/Control_Flow">aqui</a></p>
</blockquote>
<p>Com isto, a instrução que precisamos é a <code>jz</code>, que verifica se a flag <code>ZF</code> está presente:</p>
<pre><code class="language-as">.print:
	mov rsi, [rsp + 8]     
	mov rdx, 0
.calculate_size:
	inc rdx
	inc rsi
	cmp byte [rsi], 0x00     ; &lt;--- compara RSI com 0x00. Adiciona a flag ZF                                  ; quando chegar a zero
	jz .done                 ; &lt;--- desvia fluxo para a label ".done" caso a
	                         ; flag ZF esteja presente
	jmp .calculate_size
.done:
	mov rdi, STDOUT
	mov rax, SYS_write
	syscall
	ret
</code></pre>
<p>Com <code>gdb</code>, colocamos o breakpoint na linha <code>mov rdi, STDOUT</code> que é depois do loop. Caso o programa fique parado nesta linha, significa que o loop foi concluído com sucesso e os bytes da string devidamente calculados:</p>
<pre><code class="language-bash"># Breakpoint na linha &lt;mov rdi, STDOUT&gt;
(gdb) break 29

(gdb) run

# Olha o que temos aqui
(gdb) i r rdx rsi
rdx            0x3                 3
rsi            0x402003            4202499

# E se formos examinar a string (com x/s) em RSI, temos isto:
(gdb) x/s $rsi
0x402003:       ""
</code></pre>
<ul>
<li>
<p>Em RDX, temos o contador, que está em 3, que é a quantidade de bytes que será passada como terceiro argumento da syscall write. Okay, aqui ficou tudo certo.</p>
</li>
<li>
<p>Em RSI, temos <code>0x402003</code>, e o valor está vazio, ou <code>0x00</code>. Isto é um problema</p>
</li>
</ul>
<p>O problema reside no fato de que RSI precisa ter o ponteiro para a string em si, e as operações de <code>inc rsi</code> <strong>modificaram o registrador</strong>, pelo que não queremos que isto aconteça.</p>
<p>Podemos então inicialmente mover o valor que está em RSI para outro registrador temporário, que pode ser um daqueles registradores de <em>rascunho</em>, chamados de <em>draft registers</em>:</p>
<pre><code class="language-as">.print:
	mov rsi, [rsp + 8]     
	mov r9, rsi          ; aqui preservamos RSI, movendo o valor para R9
	mov rdx, 0
.calculate_size:
	inc rdx
	inc r9               ; incrementar o valor em R9, preservando assim RSI
	cmp byte [r9], 0x00  ; comparar 0x00 com R9, e não mais RSI
	jz .done
	jmp .calculate_size
.done:
	mov rdi, STDOUT       
	mov rax, SYS_write
	syscall              ; no momento da syscall, RSI está intacto, contendo
	                     ; o ponteiro para o endereço de memória onde está
	                     ; localizada a nossa queridíssima string "Hi"
	ret
</code></pre>
<p>Após estas alterações, vamos executar o programa completo:</p>
<pre><code class="language-bash">./greeting
Hi
</code></pre>
<p><em>Que dia maravilhoso!</em> Nosso programa imprime a string "Hi" calculando dinamicamente o tamanho dos bytes da string!</p>
<blockquote>
<p>Entretanto, queremos implementar a proposta inicial, não é, Leandro? O programa não tem que ler o nome da linha de comando e imprimir "Hi, Leandro"?</p>
</blockquote>
<h3>Botando mais pilha no negócio</h3>
<p>Nosso objetivo é chamar <code>./greeting</code> com argumento e assim o programa deve imprimir <code>Hi, </code> com o argumento enviado:</p>
<pre><code class="language-bash"># Objetivo, isto ainda não funciona
./greeting Leandro
Hi, Leandro
</code></pre>
<p>Se pensarmos um pouco, podemos inferir que qualquer argumento pode ser armazenado na <em>stack</em> do processo, que é quando o programa está em execução.</p>
<p>Com <code>gdb</code>, podemos confirmar isto:</p>
<pre><code class="language-bash"># Breakpoint na primeira linha do programa, depois do _start
(gdb) break 12

# Executa o programa com o argumento "Leandro"
(gdb) run Leandro

# Onde estará Leandro? Na pilha? (rsp)
#      -&gt; x de examine
#      -&gt; /8xb os primeiros 8 hexa bytes
(gdb) x /8xb $rsp
0x7fffffffe450: 0x02    0x00    0x00    0x00    0x00    0x00    0x00    0x00
</code></pre>
<p>Mas o quê significa esse número 2? Vamos examinar a stack e a ordem das informações contidas nela.</p>
<p>Voltando ao <code>gdb</code>, e se lermos os próximos 8 bytes na stack?</p>
<pre><code class="language-bash">(gdb) x /8xb $rsp + 8
0x7fffffffe458: 0xb1    0xe6    0xff    0xff    0xff    0x7f    0x00    0x00
</code></pre>
<blockquote>
<p>Lembrando que os bytes são escritos na stack em formato little-endian, ou seja estão invertidos</p>
</blockquote>
<p>Com isto, temos um hexadecimal <code>0x7fffffffe6b1</code>. Parece um endereço de memória, não?</p>
<pre><code class="language-bash"># Examinando o endereço de memória no formato de string (/s)
(gdb) x /s 0x7fffffffe6b1
0x7fffffffe6b1: "/Users/..../code/asm-x64/live"
</code></pre>
<p>Wow, temos o primeiro argumento, também chamado de <em>ARG0</em> que é o nome do programa com o caminho absoluto no sistema operacional.</p>
<p>Andando mais 8 bytes...</p>
<pre><code class="language-bash"># Endereço de memória...
(gdb) x /8xb $rsp + 16
0x7fffffffe460: 0xdf    0xe6    0xff    0xff    0xff    0x7f    0x00    0x00

# Examinando o valor que está no endereço
(gdb) x /s 0x7fffffffe6df
0x7fffffffe6df: "Leandro"
</code></pre>
<p><em>Yay!</em> Temos o nosso argumento, armazenado na stack. É o primeiro argumento, também chamado de <em>ARG1</em>.</p>
<blockquote>
<p>Se continuarmos andando na stack de 8 em 8 bytes, vamos passar por todos os argumentos (no nosso caso não há mais), e a seguir vamos chegar no vetor ambiente, que contém todas as variáveis de ambiente contidas no shell que está executando o nosso programa</p>
</blockquote>
<p>Com isto, sabemos que o primeiro argumento está localizado em <code>rsp + 16</code>:</p>
<ul>
<li>
<p><code>rsp</code>: quantidade de argumentos</p>
</li>
<li>
<p><code>rsp + 8</code>: ARG0, nome do programa</p>
</li>
<li>
<p><code>rsp + 16</code>: ARG1, primeiro argumento (se existir)</p>
</li>
<li>
<p><code>rsp + 24</code>: ARG2, segundo argumento (se existir)</p>
</li>
<li>
<p>e assim sucessivamente...até chegar no vetor de variáveis de ambiente (vetor ambiente)</p>
</li>
</ul>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zs0kmzgwigotv9drklh9.png" alt="layout de memória com stack" /></p>
<p>Sabendo que nossa <em>sub-rotina</em> <code>.print</code> já recebe uma string da stack e calcula dinamicamente o tamanho da string que foi passada, podemos passar pro topo da stack o nosso argumento (que por acaso também está na stack), e em seguida chamada a rotina <code>.print</code> novamente:</p>
<pre><code class="language-as">section .data
greet: db "Hi, ", 0

_start:
	push greet      
	call .print     
	pop rbp     

	; aqui fazemos push pro topo da stack o valor que está em RSP + 16 (ARG1)
	; utilizamos o tipo "qword" que significa "quadword"
	push qword [rsp + 16]
	call .print
	pop rbp
...
</code></pre>
<p>O quê significa <strong>quadword</strong>? Em assembly podemos definir <em>tipos de bytes</em>, que basicamente são grupos de bytes, podendo ou não caber em um registrador ou stack dependendo da arquitetura da CPU.</p>
<ul>
<li>
<p><strong>byte</strong>: especifica 1 byte (8-bit)</p>
</li>
<li>
<p><strong>word</strong>: 2 bytes (16-bit)</p>
</li>
<li>
<p><strong>dword</strong>: 4 bytes (32-bit)</p>
</li>
<li>
<p><strong>qword</strong>: 8 bytes (64-bit)</p>
</li>
<li>
<p><strong>tbyte</strong>: 10 bytes</p>
</li>
</ul>
<p>Na arquitetura x86_64, precisamos especificar que o tipo de byte adicionado na stack, quando não vier de um registrador mas sim de um lugar arbitrário na memória ou stack, tem um determinado <em>tamanho</em> em bytes.</p>
<p>Neste caso, estamos utilizando <em>qword</em> que é justamente 8 bytes (ou 64-bit) que representa a arquitetura em questão.</p>
<p>Precisamos adicionar mais um caracter, que é o <em>newline</em>, ou <code>\n</code>, no fim da mensagem. Para isto, podemos definir um dado inicializado e chamar a rotina <code>.print</code> , que já está bem "crescidinha", não?</p>
<p>Programa completo, com comentários:</p>
<pre><code class="language-as">global _start

%define SYS_write 1
%define SYS_exit 60
%define STDOUT 1

section .data
greet: db "Hi, ", 0
newline: db 0xA, 0

section .text
_start:
	push greet             ; adiciona "Hi, " na stack para print
	call .print     
	pop rbp         

	push qword [rsp + 16]  ; adiciona ARG1 na stack para print
	call .print
	pop rbp

	push newline           ; adiciona newline na stack para print
	call .print
	pop rbp
.exit:                     ; label de término do programa
	mov rdi, 0
	mov rax, SYS_exit
	syscall
.print:                    ; rotina de print no STDOUT
	mov rsi, [rsp + 8]     
	mov r9, rsi
	mov rdx, 0
.calculate_size:           ; loop para calcular tamanho da string
	inc rdx
	inc r9
	cmp byte [r9], 0x00
	jz .done
	jmp .calculate_size
.done:                     ; label para finalizar a rotina print e retornar
	mov rdi, STDOUT
	mov rax, SYS_write
	syscall
	ret
</code></pre>
<p>Rodamos o programa e:</p>
<pre><code class="language-bash">./greeting Leandro
Hi, Leandro
</code></pre>
<p><em>OMG! Eu não estou acreditando no que estou vendo!!!!!11</em></p>
<h3>Depurando o programa final com strace</h3>
<p>Com <em>strace</em>, podemos fazer o trace de syscalls do programa final. Olha que maravilha isto:</p>
<pre><code class="language-bash">$ strace ./greeting Leandro

execve("./greeting", ["./greeting", "Leandro"], 0x7ffc30f75368 /* 24 vars */) = 0
write(1, "Hi, ", 4Hi, )                     = 4
write(1, "Leandro", 7Leandro)                  = 7
write(1, "\n", 1
)                       = 1
exit(0)                                 = ?
+++ exited with 0 +++
</code></pre>
<p>Foram feitas 4 chamadas de sistema, sendo:</p>
<ul>
<li>
<p>1 write, "Hi, "</p>
</li>
<li>
<p>1 write "Leandro"</p>
</li>
<li>
<p>1 write "\n"</p>
</li>
<li>
<p>1 exit</p>
</li>
</ul>
<hr />
<h2>Falando um pouco de registradores</h2>
<p>Até o momento, vimos durante este artigo a utilização de alguns registradores que foram muito úteis para o desenvolvimento do programa, dentre eles <em>RSI</em>, <em>RAX</em>, <em>RDX</em>, <em>RSP</em>, <em>RIP</em>, <em>RFLAGS</em> e assim por diante.</p>
<p>Mas qual o <strong>propósito de cada registrador</strong>? Posso usar qualquer registrador para qualquer operação, de forma aleatória?</p>
<blockquote>
<p>De forma prática, sim. Mas nem sempre convém.</p>
</blockquote>
<p>Nada impede que o teu programa coloque qualquer valor em um registrador arbitrário. Por exemplo, com <code>gdb</code> vamos alterar alguns registradores e ver como o programa se comporta:</p>
<pre><code class="language-bash"># Breakpoint &amp; run
(gdb) break 13
(gdb) run Leandro

# Vamos alterar alguns registradores arbitrários
(gdb) set $rax = 42
(gdb) set $rdx = 33

# Confirmando que foram modificados
(gdb) i r rax rdx
rax            0x2a                42
rdx            0x21                33

# Continuando...
(gdb) continue
Continuing.
Hi, Leandro
[Inferior 1 (process 19231) exited normally]
</code></pre>
<p>Okay, podemos ver que ter mudado estes registradores para <em>qualquer valor</em> não impactou o programa. No meio do programa, provavelmente eles são sobrescritos novamente e utilizados de acordo com <em>determinada lógica</em>.</p>
<p>Mas e se alterarmos, por exemplo, um registrador como o <code>rip</code>, que é o <em>ponteiro da próxima instrução</em>?</p>
<pre><code class="language-bash"># Breakpoint &amp; run
(gdb) break 13
(gdb) run Leandro

# Antes de alterar o RIP, podemos ver qual o valor ele carrega,
# que é o ponteiro da próxima instrução
(gdb) i r rip
rip            0x401000            0x401000 &lt;_start&gt;

# Vamos alterar o registrador RIP
(gdb) set $rip = 42

# Confirmando que foi alterando
(gdb) i r rip
rip            0x2a                0x2a

# Continuando...
(gdb) continue
Continuing.
Program received signal SIGSEGV, Segmentation fault.
0x000000000000002a in ?? ()
</code></pre>
<p><em>Ouch!</em> Agora o programa não pôde ser finalizado com sucesso. Confirmamos então que nem sempre convém mudar os registradores sem <em>haver algum critério</em>.</p>
<h3>Propósito dos registradores</h3>
<p>Os registradores, e falando especificamente da arquitetura x86, seguem um <strong>propósito original</strong> para o qual foram designados. Mas também podem ser utilizados em convenções de <strong>chamadas de sistema</strong> tal como vimos na montagem das syscalls <code>write</code> e <code>exit</code>, e neste caso a utilização correta importa bastante.</p>
<p>E além disso, alguns registradores contém dados importantes para a execução do programa, tais como o <code>rip</code> e <code>eflags</code>.</p>
<ul>
<li>
<p>Propósito original</p>
</li>
<li>
<p>Convenções de chamadas</p>
</li>
<li>
<p>Funcionamento crítico do programa</p>
</li>
</ul>
<p>Apesar destas características importantes de uso dos registradores, podem haver situações em que utilizar um registrador de propósito geral é o que faz mais sentido para o programa. Vamos a seguir destacar alguns registradores e seus <em>propósitos originais</em>.</p>
<h3>Registradores de propósito geral</h3>
<p>Podemos categorizar os registradores de uso geral em 2 partes: manipulação de <em>dados diretos</em> ou <em>endereços de memória</em>.</p>
<p><strong>Dados</strong>
Registradores podem manipular dados, que chamamos de <em>valor imediato</em>, e nesta categoria podemos utilizar RAX, RBX, RCX, RDX e os registradores de rascunho que vão de R8 a R15.</p>
<ul>
<li>
<p><strong>RAX</strong>: operações aritméticas e armazenamento de resultados; também usado para o nome de chamadas de sistema em convenções de chamada (syscalls)</p>
</li>
<li>
<p><strong>RBX</strong>: ponteiro de base, utilizado para o endereço de algumas informações na memória</p>
</li>
<li>
<p><strong>RCX</strong>: geralmente usado como contador, para armazenar a quantidade de vezes que uma instrução deve ser executada</p>
</li>
<li>
<p><strong>RDX</strong>: usado para algumas operações de multiplicação e divisão, muito utilizado para armazenar o resto de operações</p>
</li>
<li>
<p><strong>R8 a R15</strong>: registradores de <em>rascunho</em> utilizados para propósito geral</p>
</li>
</ul>
<p><strong>Endereços de memória</strong>
Registradores também permitem manipular endereços de memória. Nesta categoria temos RSI, RDI, RBP e RSP.</p>
<ul>
<li>
<p><strong>RSI</strong>: utilizado como um ponteiro de origem em operações de transferências de dados, frequentemente usado em loops para iterar sobre arrays ou buffers de dados</p>
</li>
<li>
<p><strong>RDI</strong>: utilizado como ponteiro de destino em operações de transferências de dados, frequentemente usado junto com RSI</p>
</li>
<li>
<p><strong>RBP</strong>: frequentemente usado como ponteiro base em operações de memória, para referenciar variáveis locais e parâmetros de função na stack</p>
</li>
<li>
<p><strong>RSP</strong>: ponteiro para o topo da pilha (stack) do programa em execução</p>
</li>
</ul>
<h3>Registradores especiais</h3>
<p>Vamos destacar apenas 2 dos registradores considerados "especiais":</p>
<ul>
<li>
<p><strong>RFLAGS</strong>: utilizado para armazenar o estado da CPU, frequentemente modificado por instruções aritméticas e controle de paridade binária</p>
</li>
<li>
<p><strong>RIP</strong>: ponteiro de instrução, que sempre contém o endereço da próxima instrução a ser executada. Por exemplo, a instrução <code>ret</code> busca o endereço do topo da pilha e modifica o <code>rip</code> para que o programa continue a partir daquele ponto</p>
</li>
</ul>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z3ospgiir3a2nv78nm96.png" alt="tipos de registradores" /></p>
<h3>Precisamos sempre utilizar todos os 64 bits?</h3>
<p>Sabemos que registradores nesta arquitetura <strong>ocupam 64 bits de memória</strong>. Mas e quando o dado que estamos manipulando não precisa dos 64 bits? Conseguimos otimizar o uso de memória?</p>
<blockquote>
<p>A ideia seria algo do tipo "por favor me dê uma fatia dos 64 bits, não preciso de tudo"</p>
</blockquote>
<p>Historicamente, como vimos na parte II desta saga, as CPU's x86 não começaram com 64 bits. Evoluíram de 8 bits, para 16, então 32 até chegar em 64 bits.</p>
<p>Para manter compatibilidade, os registradores "legados" podem ser utilizados na arquitetura x64, e assim quando não houver necessidade de utilizar todos os bits do registrador, podemos utilizar uma <em>fatia menor</em>.</p>
<p>Por exemplo, o registrador RAX de 64-bits tem o seu equivalente de 32-bits que é o EAX, que <strong>ocupa os 32 bits mais baixos</strong>.</p>
<p>O registrador EAX, por sua vez, tem o equivalente AX de 8-bits. Dentro deste AX, podemos utilizar ainda a parte <strong>maior</strong> que se chama AH ou a parte <strong>menor</strong> que se chama AL.</p>
<blockquote>
<p>O "H" em AH vem de "high", e consequentemente "L" de AL significa "low". Óbvio, não? :P</p>
</blockquote>
<p>Sendo assim, há situações em que ao invés de:</p>
<pre><code class="language-as">mov rax, 7  ; 1 byte mas ocupa 8 bytes (64 bits)
</code></pre>
<p>E sabendo que 42 não ocupa 64 bits, podemos mudar para:</p>
<pre><code class="language-as">mov eax, 7  ; 1 byte mas ocupa 4 bytes (16 bits)
</code></pre>
<p>Ou então:</p>
<pre><code class="language-as">mov ax, 7   ; 1 byte ocupando exatamente 1 byte (8 bits)
</code></pre>
<p>Assim o programa final passa a ocupar menos memória em sua totalidade.</p>
<p>Seguindo esta lógica, podemos aplicar para todos os registradores, trazendo alguns como exemplo:</p>
<ul>
<li>
<p><strong>RAX</strong>: <code>EAX -&gt; AX -&gt; AH -&gt; AL</code></p>
</li>
<li>
<p><strong>RBX</strong>: <code>EBX -&gt; BX -&gt; BH -&gt; BL</code></p>
</li>
<li>
<p><strong>RDX</strong>: <code>RDX -&gt; DX -&gt; DH -&gt; DL</code></p>
</li>
<li>
<p><strong>R8</strong>: <code>R8W -&gt; R8B</code></p>
</li>
</ul>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wtp88ythfnm7yj2rhefw.png" alt="fatias de registradores" /></p>
<p>E assim por diante.</p>
<hr />
<h2>Uma side note sobre stack frames</h2>
<p>Depois de publicar o artigo, o <a href="https://twitter.com/rodrigogbranco">Rodrigo Gonçalves de Branco</a> decidiu dar um <a href="https://docs.google.com/document/d/10xr0Qm6jatko2dRyQYqXuToXp5DShxl6dhmBnPPUAb4/edit">feedback ultra detalhado</a> executando todos os exemplos aqui demonstrados, e um dos insights foi sobre a utilização de stack frames.</p>
<blockquote>
<p>Foi um trabalho fenomenal, meus agradecimentos ao Rodrigo</p>
</blockquote>
<p>Voltando ao exemplo dos <strong>argumentos na pilha</strong>, dentro da rotina <code>_start</code>, temos a pilha do programa com o seguinte layout:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uaiuvwiltzhlzj5bxner.png" alt="rsp antes" /></p>
<p>Quando fazemos a chamada:</p>
<pre><code class="language-as">...
	push greet    ; adiciona "Hi, " na stack para print
	call .print     
...
</code></pre>
<p>Estamos basicamente <em>manipulando a pilha original</em> do programa. O <code>push</code> vai colocar no topo da pilha (RSP) o endereço de <code>greet</code>, como demonstrado a seguir no GDB:</p>
<pre><code class="language-bash"># Breakpoint no &lt;push greet&gt;
(gdb) break 13   

(gdb) run
(gdb) next

(gdb) x $rsp
0x7fffffffe448: 0x00402000
</code></pre>
<p>Agora a pilha ficou assim:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3tqkty8gtj5olc876qtd.png" alt="layout com pilha e data" /></p>
<p>Se fizermos <code>step</code> no GDB, podemos ver que o RSP foi modificado novamente, desta vez adicionando o endereço da próxima instrução por conta da chamada <code>call</code>:</p>
<pre><code class="language-bash">(gdb) step

(gdb) x $rsp
0x7fffffffe440: 0x0040100a
</code></pre>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7kis57u2auas91bcebog.png" alt="layout com data e text" /></p>
<p><em>Isso é o que acontece com a pilha em uma simples chamada de rotina com argumentos!</em></p>
<p>Bom, sabendo disso, vemos que o argumento que precisamos está em <code>rsp + 8</code>, exatamente como no nosso programa original. <em>So far, so good</em>.</p>
<p>O problema é que podemos reparar que o RSP é modificado durante as chamadas de funções no programa. Não temos controle sobre isso.</p>
<p>E podem acontecer <em>comportamentos inesperados</em> (bugs?) quando isso ocorre, pelo simples fato de estarmos <strong>apontando dados na pilha</strong> e eles já estarem em posições que não esperávamos.</p>
<p>Para mitigar este potencial problema, podemos preservar a <strong>base da pilha</strong> em algum registrador sempre no início de cada função, desta forma cada rotina/função pode ter sua própria "versão" da pilha sem correr riscos de apontar para o dado errado.</p>
<p>Esta técnica é chamada de <strong>stack frame</strong>.</p>
<p>E é pra isso que usamos o <strong>registrador RBP</strong>! No <em>prólogo</em> de cada rotina, adicionamos o <code>rbp</code> na pilha e em seguida colocamos o ponteiro de <code>rsp</code> dentro do registrador <code>rbp</code>, igualando assim ambos registradores:</p>
<pre><code class="language-as">_start:
    push rbp
    mov rbp, rsp
....
</code></pre>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/si286ldan8b9swswr9q0.png" alt="rsp e rbp" /></p>
<p>Repare que esta técnica consiste em <em>igualar</em> RSP com RBP, assim pode-se de forma segura manipular o <em>ponteiro em RBP</em>, pois mesmo RSP sendo modificado pelo programa, RBP continua intacto.</p>
<p>Continuando com o programa:</p>
<pre><code class="language-as">push rbp
mov rbp, rsp
....
push greet
call .print
....
</code></pre>
<p>Constatamos no GDB que a stack foi alterada, portanto RSP foi modificado para apontar para o endereço da próxima instrução, ao passo que RBP continua apontando pro valor anterior:</p>
<pre><code class="language-bash"># RBP 
(gdb) x $rbp
0x7fffffffe448: 0x00000000

# RSP aponta para o endereço da próxima instrução 
# antes da chamada da rotina
(gdb) x $rsp
0x7fffffffe438: 0x0040100e

# RSP + 8 aponta para o primeiro argumento da rotina
(gdb) x $rsp + 8
0x7fffffffe440: 0x00402000

# RSP + 16 aponta para o mesmo valor de RBP (base da pilha),
# ou seja, `RBP = RSP + 16` neste caso porque houve um PUSH
# explícito do argumento e também outro push feito pelo CALL
(gdb) x $rsp + 16
0x7fffffffe448: 0x00000000
</code></pre>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a3fjvaxlpm4g8c41jnl8.png" alt="rbp  = rsp + 16" /></p>
<p>E modificando a rotina <code>.print</code> para também ter seu próprio stack frame, como fica a pilha depois de executar:</p>
<pre><code class="language-as">.print:
    push rbp
    mov rbp, rsp
....
</code></pre>
<p>Analisando com GDB:</p>
<pre><code class="language-bash">(gdb) x $rbp
0x7fffffffe430: 0xffffe448

(gdb) x $rsp
0x7fffffffe430: 0xffffe448
</code></pre>
<p>RSP e RBP ficaram igualados novamente, dando uma característica de stack frame, preservando a pilha como podemos ver na imagem a seguir:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oyrhiaqltlra50b8fi2w.png" alt="stack frame" /></p>
<p>Portanto, o argumento da rotina, ao invés de ser <code>rsp + 8</code>, passa a ser <code>rbp + 16</code> por conta da stack frame, ficando da seguinte forma:</p>
<pre><code class="language-as">.print:                  
	push rbp
	mov rbp, rsp

	mov rsi, [rbp + 16]     
	mov r9, rsi
	mov rdx, 0
</code></pre>
<p><em>Uma coisa importante</em>: ao final de cada rotina, antes do <em>retorno</em>, devemos fazer <code>pop</code> do topo da pilha para voltar ao estado original antes do <code>push rbp</code> feito no início da rotina:</p>
<pre><code class="language-as">push rbp
mov rbp, rsp
....
pop rbp
ret
</code></pre>
<p>Desta forma, ao fazer o <code>pop rbp</code>, o que está em RSP é justamente o endereço de retorno antes da chamada da função:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4kbrojascqyspoa4lhjl.png" alt="pop do rbp antes do retorno" /></p>
<p>Ao continuar com o programa, a instrução <code>ret</code> (já falamos sobre ela anteriormente) faz <em>pop</em> do topo da pilha (RSP) e continua a execução do programa na próxima instrução:</p>
<pre><code class="language-as">_start:
	push rbp
	mov rbp, rsp     ; &lt;--- iguala RSP e RBP

	push greet       ; &lt;--- adiciona &lt;greet&gt; na pilha
	call .print      ; &lt;--- adiciona ponteiro da próxima 
                         ; instrução na pilha
	pop rax          ; &lt;--- faz pop de &lt;greet&gt; da pilha      

............
.print:                 
	push rbp         
	mov rbp, rsp     ; &lt;--- iguala RSP e RBP

	mov rsi, [rbp + 16]     
        .............
	syscall
	pop rbp          ; &lt;--- remove frame RBP da pilha
	ret              ; &lt;--- faz pop do pointeiro da 
                         ; próxima instrução e atualiza RIP
</code></pre>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nrhswxyhluvlc9yvlyem.png" alt="layout depois do ret" /></p>
<p>Quando o fluxo volta para quem chamou a rotina, a próxima instrução deve ser sempre o <code>pop</code> dos argumentos que entraram na pilha.</p>
<p>Neste caso no exemplo anterior estamos fazendo <em>pop</em> do argumento e descartando o valor em RAX com <code>pop rax</code>, deixando assim a pilha em seu estado anterior à chamada da rotina:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uxp5nup5nzqs19hx3nwb.png" alt="final" /></p>
<p>Ao fim do programa (rotina <code>_start</code>), devemos também fazer <code>pop rbp</code>, assim a pilha volta ao estado original de quando foi iniciado o programa.</p>
<p>Código completo:</p>
<pre><code class="language-as">global _start

%define SYS_write 1
%define SYS_exit 60
%define STDOUT 1

section .data
greet: db "Hi, ", 0
newline: db 0xA, 0

section .text
_start:
	push rbp               ; &lt;-- cria um stack frame
	mov rbp, rsp           ; para preservar a pilha

	push greet             ; adiciona "Hi, " na pilha
	call .print            ; chama sub-rotina
	pop rax                ; remove "Hi, " da pilha

	push qword [rbp + 24]  ; adiciona argumento na pilha
	call .print            ; chama sub-rotina
	pop rax                ; remove argumento da pilhha

	push newline           ; adiciona newline na pilha
	call .print            ; chama-subrotina
	pop rax                ; remove newline da pilha

	pop rbp                ; remove RBP da pilha, 
                               ; retornando ao estado original
.exit:               
	mov rdi, 0
	mov rax, SYS_exit
	syscall                ; termina o programa
.print:                   
	push rbp               ; &lt;-- cria um stack frame
	mov rbp, rsp           ; para preservar a pilha

	mov rsi, [rbp + 16]     
	mov r9, rsi
	mov rdx, 0
.calculate_size:               ; loop para calcular tamanho
	inc rdx
	inc r9
	cmp byte [r9], 0x00
	jz .done
	jmp .calculate_size
.done:                     
	mov rdi, STDOUT
	mov rax, SYS_write
	syscall

	pop rbp                ; &lt;--- remove RBP da pilha, 
                               ; retornando ao estado anterior

	ret                    ; &lt;--- retorna fluxo para o
                               ; estado anterior
</code></pre>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4zqxli9xe88yxloe21ht.png" alt="voltando ao estado original da pilha" /></p>
<p>É isto. Esta seção foi apenas uma demonstração de como utilizar boas práticas de manipulação da pilha quando utilizamos argumentos em funções, através da técnica de <em>criar um frame</em> como <strong>base da pilha</strong> com o registrador RBP.</p>
<hr />
<h2>Conclusão</h2>
<p>É isto, pessoal. Esta parte da saga foi bastante densa. Passamos pela criação de um programa simples em Assembly, ao passo em que íamos depurando o programa com ferramentas como <em>strace</em>, <em>size</em> e <strong>muito gdb</strong>.</p>
<p>Também aprendemos sobre labels, tipos de registradores, desvio de fluxo com jmp, call, ret, <strong>muita stack</strong>, depurando tudo e mais um pouco, loops, FLAGS e aritmética de ponteiro.</p>
<p>Apesar de ter sido muito denso, os tópicos aqui abordados servirão de base para entendermos o próximo artigo que já começa pesado com syscalls de rede, para iniciarmos o nosso tão esperado web server.</p>
<p>Nos vemos no próximo artigo!</p>
<hr />
<h2>Referências</h2>
<sub>
Mnemonics
https://en.wikipedia.org/wiki/Mnemonic
Comparison of Assemblers
https://en.wikipedia.org/wiki/Comparison_of_assemblers
Linker (computing)
https://en.wikipedia.org/wiki/Linker_(computing)
Assembly x86 tutorial
https://www.tutorialspoint.com/assembly_programming/index.htm
Data segment
https://en.wikipedia.org/wiki/Data_segment
FLAGS register
https://en.wikipedia.org/wiki/FLAGS_register
Debugging with GDB
https://ncona.com/2019/12/debugging-assembly-with-gdb/
GDB command reference
https://visualgdb.com/gdbreference/commands/
GDB cheatsheet
https://cs.brown.edu/courses/cs033/docs/guides/gdb.pdf
[Vídeo] Introdução ao GNU Debugger - Blau Araújo
https://www.youtube.com/watch?v=t9OKpBKbJ4Q
</sub>
]]></description>
<pubDate>2024-05-13</pubDate>
</item>
<item>
<title>Construindo um web server em Assembly x86, parte III, código de máquina</title>
<link>https://leandronsp.com/articles/construindo-um-web-server-em-assembly-x86-parte-iii-codigo-de-maquina-bgk.html</link>
<guid>https://leandronsp.com/articles/construindo-um-web-server-em-assembly-x86-parte-iii-codigo-de-maquina-bgk.html</guid>
<description><![CDATA[<p>Agora que já temos uma <a href="https://leandronsp.com/articles/construindo-um-web-server-em-assembly-x86-parte-ii-historia-e-arquitetura-2jb9">base de entendimento</a> sobre hierarquia de memória, arquitetura de CPU e registradores, vamos aplicar estes conceitos em exemplos práticos: construindo programas de computador.</p>
<p><em>Mas o que é um programa de computador?</em></p>
<p>Teremos a resposta para esta pergunta ao longo deste artigo. Vamos abordar muitos conceitos, desde código de máquina (o mais importante na minha opinião), a sistemas de numeração binário, decimal e hexadecimal.</p>
<p>Iremos também compreender opcodes, chamadas de sistema, modo kernel, libc, ASCII, standard streams; e alterar arquivos binários em hexadecimal.</p>
<p>Ao final deste artigo vamos estar em um patamar de entendimento mais holístico de como um programa é interpretado na CPU.</p>
<p>Ainda não entraremos em Assembly. Foi escolhido desta forma pois o intuito com esta saga é detalhar ao máximo como as peças de encaixam, e acredito que trazer Assembly sem explicar outros conceitos primordiais pode confundir bastante.</p>
<p>Também não é esperado que você escreva os códigos de máquina deste artigo, pois aqui <a href="https://github.com/leandronsp/monica/blob/main/example">neste link</a> providencio o binário já pronto para que você possa acompanhar com as ferramentas que irei utilizar. Basta apenas baixar o arquivo binário no link fornecido e atribuir permissão de execução com <code>chmod +x</code>, se necessário.</p>
<blockquote>
<p>Lembrando que é importante que esteja em um ambiente Linux, caso contrário não irá funcionar. Se estiver em outro ambiente e não puder virtualizar, poderá acompanhar esta saga apenas lendo, pois a ideia é também trazer muitos conceitos fundamentais de baixo-nível</p>
</blockquote>
<p>Ainda não será o código do web server, o programa proposto neste post é bastante simples, mas estamos quase lá. Vamos focar em conceitos fundamentais para que futuros artigos, que cobrem o desenvolvimento do web server, possam ser melhor compreendidos.</p>
<p>Sem mais delongas, prepare-se para a partir de agora entrar numa espiral de código de máquina e manipulação de memória.</p>
<hr />
<h2>Agenda</h2>
<ul>
<li>
<p><a href="#o-que-%C3%A9-um-programa-de-computador">O que é um programa de computador</a></p>
<ul>
<li><a href="#sistemas-operacionais-e-processos">Sistemas Operacionais e Processos</a></li>
<li><a href="#um-programa-deve-sempre-terminar">Um programa deve sempre terminar</a></li>
</ul>
</li>
<li>
<p><a href="#nosso-primeiro-programa">Nosso primeiro programa</a></p>
<ul>
<li><a href="#a-linguagem-das-cpus-o-sistema-bin%C3%A1rio">A linguagem das CPU's, o sistema binário</a></li>
<li><a href="#o-famoso-sistema-decimal">O famoso sistema decimal</a></li>
<li><a href="#hexadecimal-o-queridinho-dos-computadores">Hexadecimal, o queridinho dos computadores</a></li>
<li><a href="#opcodes">Opcodes</a></li>
<li><a href="#endianness">Endianness</a></li>
</ul>
</li>
<li>
<p><a href="#nosso-segundo-programa">Nosso segundo programa</a></p>
<ul>
<li><a href="#alocando-dados-na-mem%C3%B3ria-do-programa">Alocando dados na memória do programa</a></li>
<li><a href="#ascii">ASCII</a></li>
<li><a href="#syscalls">Syscalls</a></li>
<li><a href="#montando-a-syscall-write">Montando a syscall write</a></li>
<li><a href="#montando-a-syscall-exit">Montando a syscall exit</a></li>
</ul>
</li>
<li>
<p><a href="#manipulando-o-nosso-programa">Manipulando o nosso programa</a></p>
</li>
<li>
<p><a href="#a-vida-de-quem-programa-%C3%A9-assim">A vida de quem programa é assim?</a></p>
</li>
<li>
<p><a href="#conclus%C3%A3o">Conclusão</a></p>
</li>
<li>
<p><a href="#refer%C3%AAncias">Referências</a></p>
</li>
</ul>
<hr />
<h2>O que é um programa de computador</h2>
<p>Como já vimos na parte II, a função primordial de uma CPU é ler uma instrução da memória, decodificar, executar e armazenar o resultado de volta na memória.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/74pip1fwevxzhlgtskrq.png" alt="CPU decodifica instrução" /></p>
<p>Então a grosso modo, um programa de computador é um conjunto de instruções pra a CPU processar. Em um cenário típico, teríamos diversos programas diferentes <strong>lendo e escrevendo</strong> da mesma memória do computador:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v04zatlsnnkesdegjknq.png" alt="programas e memória" /></p>
<p>Mas um potencial problema, é que neste cenário poderíamos ter dois diferentes programas acessando ou modificando o mesmo endereço de memória:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rcdxz981arzq3d9h5e1b.png" alt="conflito em memória" /></p>
<p>Pra resolver isto, precisamos agrupar as instruções de um programa de modo a "isolar" de outros programas que também estão rodando no computador.</p>
<p>É aí que entra um dos papéis do <strong>sistema operacional</strong> com o conceito de <em>processos</em>.</p>
<blockquote>
<p>Lembrando que nesta saga, vamos focar apenas em sistemas UNIX-like, mais precisamente distruibuições GNU/Linux</p>
</blockquote>
<h3>🔵 Sistemas Operacionais e Processos</h3>
<p>Cada programa executado no SO é encapsulado em uma estrutura chamada <em>processo</em>, que vai ter uma área virtual na memória principal.</p>
<p>Na prática, cada programa vai ter seu próprio "0x10000", isolado dos demais.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ra2p8vkdu9pu3tgthvsk.png" alt="sistemas operacionais e processos" /></p>
<h3>🔵 Um programa deve sempre terminar</h3>
<p>Como o SO aloca recursos de memória (dentre outros) para o processo, nosso programa precisa indicar quando termina.</p>
<p>Desta forma aquele espaço reservado de memória fica livre para ser utilizado por outro processo. Isto evita problemas como vazamento de memória entre outros.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bh2z34wvdqii8nz7w3bm.png" alt="program exit" /></p>
<p>Caso isto não seja feito, o SO vai lançar uma exceção e o programa não pode ser admitido como processo.</p>
<hr />
<h2>Nosso primeiro programa</h2>
<p>Vamos trabalhar inicialmente com um exemplo bastante simples. Um programa que <em>não faz nada</em>.</p>
<blockquote>
<p>Nossa Leandro, como assim um programa que não faz nada?</p>
</blockquote>
<p>Sim, parece estranho para linguagens de mais alto nível. Mas pensando em CPU isto já é alguma coisa, pelo que precisamos de ao menos algumas instruções para um programa que "não faz nada": <strong>o programa precisa terminar</strong>, lembra?.</p>
<h3>🔵 A linguagem das CPU's, o sistema binário</h3>
<p>Pensando na CPU como um dispositivo eletrônico, esta só entende pulso elétrico. Mas conseguimos abstrair tais pulsos como 0 ou 1.</p>
<p>Consequentemente, quando falamos em código de máquina para uma CPU estamos falando em instruções que utilizam <em>sistema binário</em>, composto de 0 ou 1.</p>
<p>Nosso programa que não faz nada além de "terminar" pode ser representado então pelo seguinte conjunto de instruções:</p>
<blockquote>
<p>Não se preocupe em escrever o programa. Por enquanto são só exemplos de código de máquina para que possamos entender bem os conceitos</p>
</blockquote>
<pre><code>10111111 00000001 00000000 00000000 00000000   
10111000 00111100 00000000 00000000 00000000  
00001111 00000101                            
</code></pre>
<p>No sistema binário, cada símbolo é chamado de <strong>bit</strong>. Este simples programa tem 12 conjuntos de 8 bits cada. <em>Conte você mesmo para confirmar!</em></p>
<p>O <strong>sistema binário</strong> tem esse nome porque só fornece dois tipos de símbolos para representar números. Vamos contar:</p>
<pre><code>0
1
</code></pre>
<p>Acabou. Com 1 dígito podemos ter 2 combinações apenas. Mas e se quisermos representar mais números? Aí nos resta ficar combinando com mais dígitos.</p>
<p>Para 2 dígitos, conseguimos aumentar para 4 combinações:</p>
<pre><code>00
01
10
11
</code></pre>
<p>Se quisermos continuar, temos que entrar com 3 dígitos, sempre começando com o mais à esquerda possível, o que nos dá 8 combinações:</p>
<pre><code>000 
001 
010 
100
100 
101 
110 
111
</code></pre>
<p>Com isto, temos um padrão:</p>
<ul>
<li>
<p>1 dígito: 2 combinações</p>
</li>
<li>
<p>2 dígitos: 4 combinações</p>
</li>
<li>
<p>3 dígitos: 8 combinações</p>
</li>
</ul>
<p>Repare no padrão de exponenciação. Estamos pegando o número 2 como base e aplicando o número do dígito como expoente:</p>
<p><em>numero de símbolos ^ número de dígitos</em></p>
<ul>
<li>
<p>2^1 = 2</p>
</li>
<li>
<p>2^2 = 4</p>
</li>
<li>
<p>2^3 = 8</p>
</li>
</ul>
<p>Extrapolando para 4 ou mais dígitos, podemos chegar na seguinte conclusão:</p>
<ul>
<li>
<p>2^4 = 16 combinações</p>
</li>
<li>
<p>2^5 = 32 combinações</p>
</li>
<li>
<p>2^6 = 64 combinações</p>
</li>
<li>
<p>2^7 = 128 combinações</p>
</li>
<li>
<p>2^8 = 256 combinações</p>
</li>
</ul>
<p>E assim por diante...</p>
<blockquote>
<p>Você tá brincando com minha cara, né Leandro? Vim aqui pra ficar escovando bit?</p>
</blockquote>
<p>Não exatamente. No programa, cada conjunto ali de 8 bits (chamado de <strong>byte</strong>) tem um significado para a CPU, o que faz nosso programa ter 12 bytes.</p>
<p>E como não somos uma CPU, estamos nada preocupados em representar instruções em bits, vamos então converter para o sistema decimal para conseguirmos representar nosso <strong>mesmo programa</strong> de forma mais simples e intuitiva.</p>
<h3>🔵 O famoso sistema decimal</h3>
<p>Já estamos habituados com o sistema decimal. Muitos números no nosso dia-dia são representados através do sistema decimal.</p>
<p>Falamos de números como "dez", "cento e quinze", "quarenta e dois" sem qualquer problema, pois foi o que aprendemos desde a primeira infância. Nosso cérebro já fixou o aprendizado tão intrinsicamente, que sequer pensamos que se trata de um sistema de numeração como qualquer outro.</p>
<p>Vamos por um momento <em>esquecer</em> que sabemos sistema decimal e aplicar as mesmas regras que aplicamos para o sistema binário.</p>
<p>Repetindo mais uma vez, no sistema binário temos à disposição apenas dois símbolos: 0 e 1.</p>
<blockquote>
<p>E no decimal?</p>
</blockquote>
<p>Temos <strong>dez</strong> símbolos à disposição, que são:</p>
<pre><code>0 1 2 3 4 5 6 7 8 9
</code></pre>
<p>Tal como no sistema binário, com 1 dígito apenas temos essas 10 possibilidades acima.</p>
<p>Já chegou no nove? Acabaram as combinações? Não tem problema, vamos subir pra <em>dois dígitos</em> sempre começando pelo dígito mais à equerda possível:</p>
<pre><code>00 01 02 03 04 05 06 07 08 09
10 11 12 13 14 15 16 17 18 19
20 21 22 23 24 25 26 27 28 29
...
...........................99
</code></pre>
<p>Olha só, com apenas dois dígitos no sistema decimal, podemos combinar 100 números diferentes!</p>
<p>O padrão é o mesmo no sistema binário, podemos logo aplicar exponenciação da base, sendo:</p>
<p><em>numero de símbolos ^ número de dígitos</em></p>
<p>Portanto:</p>
<ul>
<li>
<p>10^2 = 100 combinações</p>
</li>
<li>
<p>10^3 = 1000 combinações</p>
</li>
<li>
<p>10^4 = 10000 combinações</p>
</li>
<li>
<p>etc etc etc</p>
</li>
</ul>
<h3>Convertendo binário em decimal</h3>
<p>Uma vez que entendendo sistemas de numeração binário e decimal, podemos "compactar" nosso programa inicial de binário para decimal de modo a termos uma leitura mais intuitiva, não?</p>
<p>Aplicando a regra de exponenciação, não fica difícil fazer a conversão:</p>
<ul>
<li>
<p>0 é sempre 0, pois este símbolo está presente em ambos sistemas de numeração</p>
</li>
<li>
<p>Mesmo vale para 1, pois está presente em ambos</p>
</li>
</ul>
<p>Se quisermos então converter <code>10</code> (que é o próximo número depois de 1 em binário) de binário pra decimal, vamos aplicar a seguinte regra:</p>
<p><em>dígito x 2^posição do dígito</em></p>
<p>...<em>somando o resultado</em> de cada operação em dígito, onde a posição <strong>mais à direita possível</strong> começa com <em>zero</em>, pois de acordo com a senhora matemática, qualquer número elevado a <em>zero</em> é <em>UM</em>*.</p>
<p>Com isto:</p>
<ul>
<li>10 = <em>(1 x 2^1) + (0 x 2^0)</em> = 2 + 0 = <strong>2</strong></li>
</ul>
<p>Vamos extrapolar um pouquinho?</p>
<ul>
<li>
<p>11 = <em>(1 x 2^1) + (1 x 2^0)</em> = 2 + 1 = <strong>3</strong></p>
</li>
<li>
<p>100 = <em>(1 x 2^2) + (0 x 2^1) + (0 x 2^0)</em> = 2 + 0 + 0 = <strong>4</strong></p>
</li>
</ul>
<p>Yay! <em>Nice, uh?</em></p>
<p>Agora vamos converter <em>byte a byte</em> do nosso programa:</p>
<pre><code>10111111 00000001 00000000 00000000 00000000   
10111000 00111100 00000000 00000000 00000000  
00001111 00000101                            
</code></pre>
<ul>
<li>
<p>10111111 = <em>(1 x 2^7) + (0 x 2^6) ...</em> = <strong>191</strong></p>
</li>
<li>
<p>00000001 = <em>(0 x 2^7) + (0 x 2^6) ... + (1 + 2^0)</em> = <strong>1</strong></p>
</li>
<li>
<p>etc etc etc até chegar ao 12º byte</p>
</li>
</ul>
<p>Temos então um programa convertido para decimal da seguinte forma:</p>
<pre><code>191 1 0 0 0
184 60 0 0 0
15 5
</code></pre>
<p>Entretanto, não é comum representar sistema decimal para instruções de CPU, pois cada byte (8 bits) não seria dividido em partes iguais no sistema decimal, o que poderia causar um <em>desalinhamento</em>, criando buracos desnecessários na memória.</p>
<blockquote>
<p>Uma forma de ver este problema é que o sistema decimal composto de 10 símbolos não é divisível por 8 (resto 2), que é a quantidade de bits em um byte</p>
</blockquote>
<p>E se tivéssemos um sistema de numeração com uma <em>quantidade de dígitos</em> que fosse divisível por 8?</p>
<p>Sabendo que 16 é divisível por 8 (resto 0) e não causaria desalinhamento entre bits pra representar um programa, será que existe um sistema de numeração de 16 símbolos para podermos <strong>compactar ainda mais</strong> a representação textual do nosso programa?</p>
<p>Sim, estamos falando do <strong>sistema hexadecimal</strong>.</p>
<h3>🔵 Hexadecimal, o queridinho dos computadores</h3>
<p>Certamente você já ouviu falar, viu ou até praticou hexadecimal. Similar ao que fizemos com sistema decimal, vamos esquecer tudo o que sabemos sobre hexadecimal e aplicar algumas regras.</p>
<p>Quantos símbolos temos à disposição? 16.</p>
<blockquote>
<p>Daí o nome: <strong>hexa</strong>, representando 6, e <strong>decimal</strong> representando 10. DEZ + SEIS!!!!!111</p>
</blockquote>
<p>Os primeiros 10 símbolos são exatamente como no decimal:</p>
<pre><code>0 1 2 3 4 5 6 7 8 9
</code></pre>
<p>E quanto aos 6 <strong>símbolos</strong> restantes? Poderíamos representar emojis, gifs animados ou derivados de batata, mas seria muito mais simples representarmos as primeiras 6 letras do alfabeto, não?</p>
<pre><code>0 1 2 3 4 5 6 7 8 9 A B C D E F
</code></pre>
<p>As regras pra combinar são as mesmas, sempre do mais à esquerda possível:</p>
<pre><code>0 1 2 3 4 5 6 7 8 9 A B C D E F
10 11 12 13 14 15 16 17 18 19 ?
</code></pre>
<p>Qual seria o próximo? 20?</p>
<blockquote>
<p>Não, jovem, vamos combinar com as letras restantes</p>
</blockquote>
<pre><code>...
19
1A
1B
1C
1D
1E
1F
</code></pre>
<p>Agora sim, as combinações com o dígito 2:</p>
<pre><code>20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 
30 31 32 33 34 ............................. 3F 
...........................99................9F
100
</code></pre>
<p>Com apenas 2 dígitos no sistema hexadecimal, podemos ter 256 combinações de números diferentes!</p>
<ul>
<li>
<p>em binário, 2 dígitos = 4 combinações</p>
</li>
<li>
<p>em decimal, 2 dígitos = 100 combinações</p>
</li>
<li>
<p>em hexa, 2 dígitos = 256 combinações</p>
</li>
</ul>
<p>Por exemplo, para termos 256 combinações com 2 símbolos <code>FF</code>, precisaríamos de 8 símbolos <code>1111 1111</code> no sistema binário, <strong>obviamente ocupando mais espaço</strong> pra representar e visualizar programas.</p>
<p><em>O céu é o limite 🚀</em></p>
<p>Aplicando regra de exponenciação para convertermos pra decimal, vamos ter o seguinte:</p>
<ul>
<li>
<p>do 0 ao 9 é tudo igual</p>
</li>
<li>
<p>A = 10, B = 11, C = 12, D = 13, E = 14, F = 15</p>
</li>
</ul>
<p>Onde:</p>
<p><em>dígito do sistema * 16^posição do dígito</em></p>
<p>Com isto, convertendo <code>1A</code> e <code>FF</code> para decimal, ficaria:</p>
<ul>
<li>
<p>1A = <em>(1 * 16^1) + (10 * 16^0)</em> = 16 + 10 = <strong>26</strong></p>
</li>
<li>
<p>FF = <em>(15 * 16^1) + (15 * 16^0)</em> = 240 + 15 = <strong>255</strong></p>
</li>
</ul>
<blockquote>
<p>Ok, mas precisamos converter de binário para hexa</p>
</blockquote>
<p>Com razão, como podemos converter o byte <strong>11111111</strong> para hexadecimal? Dá pra fazer por dedução da conversão decimal, por exemplo:</p>
<ul>
<li>
<p>sabendo que o byte <strong>11111111</strong> representa 255 em decimal, e sabendo que <code>FF</code> em decimal é 255,  portanto concluímos que <code>11111111 = FF</code></p>
</li>
<li>
<p>se dividirmos o byte em 2 partes, podemos calcular que <code>1111 = F</code>, portanto chegamos no mesmo resultado</p>
</li>
</ul>
<p>Geralmente empregamos a técnica de dividir o byte em 2 partes de 4 bits cada. Assim fica mais fácil visualizar.</p>
<p>O simples programa original escrito em binário, fica então convertido em hexadecimal da seguinte forma:</p>
<pre><code>BF 01 00 00 00 
B8 3C 00 00 00 
0F 05          
</code></pre>
<p>Opa! Já conseguimos ter uma leitura mais intuitiva, certo? Mas que raios significa <code>BF 01</code>, <code>B8 3C</code> ou <code>0F 05</code> na CPU?</p>
<h3>🔵 Opcodes</h3>
<p>Cada CPU possui uma arquitetura específica. Falando de x64 (ou x86_64), esta traz um conjunto de <strong>opcodes</strong> no manual que representam as instruções disponíveis, registradores entre outras operações de CPU.</p>
<p>De acordo com o <a href="http://ref.x86asm.net/coder64.html">manual</a>:</p>
<ul>
<li>
<p>o opcode <code>BF</code> move um valor imediato para o registrador RDI</p>
</li>
<li>
<p><code>01 00 00 00</code>: valor imediato hexa que representa <code>1</code> em decimal, mas na ordem inversa no formato <strong>little-endian</strong> (vamos falar de endianness mais a seguir)</p>
</li>
<li>
<p>o opcode <code>B8</code> move um valor imediato para o registrador RAX</p>
</li>
<li>
<p><code>3C 00 00 00</code>: valor imediato hexa que representa <code>60</code> em decimal, mas na ordem inversa no formato <strong>little-endian</strong></p>
</li>
<li>
<p>o opcode <code>0F 05</code> entra no modo "kernel" do SO e aguarda a resposta de uma chamada de sistema (syscall)</p>
</li>
</ul>
<p>Mas por quê os bytes são representados na ordem inversa nesta arquitetura de CPU?</p>
<h3>🔵 Endianness</h3>
<p>O conceito de <strong>endianness</strong> está relacionado com a forma que CPU's lêem e processam bytes na memória.</p>
<p>Vamos trazer o exemplo de um byte em binário, <code>10000001</code>, que sabemos que é <code>129</code> em decimal. Prestando atenção nos expoentes:</p>
<p><em>2^7 + 2^6 + 2^5 + 2^4 + 2^3 + 2^2 + 2^1 + 2^0</em></p>
<ul>
<li>
<p>o bit mais à esquerda, <em>1 x 2^7</em> = 128</p>
</li>
<li>
<p>somado com o bit mais à direita, <em>1 x 2^0</em> = 1</p>
</li>
<li>
<p>o restante dos bits está tudo a zero, não precisam entrar pra soma</p>
</li>
</ul>
<p>Podemos inferir que os bits mais à direita <em>não têm tanto peso</em> no valor final, por isso são chamados <strong>bits menos significativos</strong>.</p>
<blockquote>
<p>O bit da direita incorporou apenas o valor <strong>1</strong> pro resultado final</p>
</blockquote>
<p>Da mesma forma, os bits mais à esquerda <em>têm mais peso</em> no valor final, por isso são chamados de <strong>bits mais significativos</strong>.</p>
<blockquote>
<p>O bit da esquerda incorporou <strong>128</strong> pro resultado final, trazendo mais significância</p>
</blockquote>
<p>Esta propriedade de definir significância de bits é chamada de <em>endianness</em>. Diferentes arquiteturas de CPU podem decidir ler do mais significativo ao menos significativo (padrão intuitivo de leitura, big-endian) ou do menos significativo ao mais significativo (little-endian).</p>
<p>A decisão passa por fatores históricos ou por facilitar manipulação de ponteiros. Diferentes sistemas podem decidir por inverter a leitura/escrita ou não dos bytes.</p>
<p>Na CPU x86_64, o formato é <strong>little-endian</strong>, portanto em hexa o valor de 4 bytes <code>00 0D 00 3C</code> passa a ser <code>3C 00 0D 00</code> no formato little-endian.</p>
<p>Concluindo, vamos adicionar comentários ao nosso pseudo-programa:</p>
<pre><code>BF 01 00 00 00  ; MOVE 1 PARA RDI
B8 3C 00 00 00  ; MOVE 60 PARA RAX
0F 05           ; CHAMADA DE SISTEMA (SYSCALL)
</code></pre>
<p>Como a CPU sabe qual syscall deve executar? Por padrão, o número da syscall fica no registrador <code>RAX</code>. E como saber qual o número da syscall?</p>
<p>Neste <a href="https://x64.syscall.sh/">link</a>, temos uma lista completa de todas as syscalls da arquitetura x64, e ali podemos conferir que a syscall <code>exit</code> do kernel representa o número 60 decimal, ou <em>3C</em> hexa. É exatamente o que a instrução <code>B8 3C</code> está fazendo!</p>
<p>Mais a seguir neste artigo vamos aprofundar no mundo das syscalls e chamadas do kernel.</p>
<hr />
<h2>Nosso segundo programa</h2>
<p>Até agora vimos apenas o código de máquina de um programa que não faz nada (apenas termina), mas foi bastante útil para entendermos sistema binário, hexadecimal e outros conceitos.</p>
<blockquote>
<p>Continuaremos ainda explorando código de máquina. Não precisa escrever nada, mas pode acompanhar com o <a href="https://github.com/leandronsp/monica/blob/main/example">código que disponibilizei</a> no início do artigo e rodar os comandos em Linux</p>
</blockquote>
<p>Vamos agora elaborar um hipotético programa que imprime "Hello, World" na saída (STDOUT). Para isto, devemos:</p>
<ul>
<li>alocar memória para a string "Hello, World"</li>
</ul>
<blockquote>
<p>Sim, dados ficam na memória junto com o programa, lembra?</p>
</blockquote>
<ul>
<li>escrever a string na saída STDOUT, que é a saída padrão do programa (em outras palavras, a "tela")</li>
</ul>
<blockquote>
<p>Se quiser mais detalhes do que é STDOUT, standard streams e redirecionamento de streams, sugiro ler <a href="https://leandronsp.com/articles/entendendo-unix-pipes-3k56">outros artigos</a> que escrevi sobre UNIX pipes</p>
</blockquote>
<ul>
<li>terminar o programa</li>
</ul>
<p><em>Alocar, imprimir, terminar</em>. Escrever no STDOUT é uma chamada de sistema, e terminar o programa é <strong>outra</strong> chamada de sistema. Portanto, temos:</p>
<ul>
<li>
<p>uma alocação de dados na memória do programa</p>
</li>
<li>
<p>2 chamadas de sistema</p>
</li>
</ul>
<h3>🔵 Alocando dados na memória do programa</h3>
<p>Em linguagem de máquina, fazemos alocação byte a byte, e sabendo que queremos alocar "Hello, World" <em>literalmente</em>, como representar cada letra, o caracter <em>vírgula</em> e o caracter <em>espaço</em> na memória?</p>
<p>Precisamos de uma tradução dos caracteres para representação decimal ou hexadecimal. É isso mesmo que você está lendo, vamos entrar em <strong>ASCII</strong>.</p>
<h3>🔵 ASCII</h3>
<p>ASCII (American Standard Code for Information Interchange), é um padrão para codificação de caracteres em comunicação eletrônica, criado nos anos 60.</p>
<p>O padrão ASCII estabeleceu inicialmente 7 bits para cada caracter e foi concebido para suportar caracteres presentes somente na língua inglesa. Por conta desta limitação, tem suporte para um máximo de 128 caracteres (2^7) que abordam os dígitos decimais, caracteres especais e letras do alfabeto, maiúsculas e minúsculas.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t80bwvjweywmtmghmnvt.png" alt="tabela ASCII" /></p>
<p>Mais tarde veio uma extensão da tabela com um bit a mais portanto suportando mais caracteres como acentuações, porém ainda com limitações para suportar outros idiomas e caracteres especiais.</p>
<p>Tempos depois surgiu o padrão <strong>Unicode</strong>, que adiciona a capacidade de codificação de tamanho variável, permitindo assim uma multitude de caracteres e alfabetos de diversos idiomas.</p>
<blockquote>
<p>Unicode também contempla a mesma tabela ASCII nos primeiros 128 caracteres por questões de retrocompatibilidade em sistemas. Portanto, apesar de sistemas modernos utilizarem esquemas de codificação Unicode tais como UTF-8, neste artigo focaremos na terminologia ASCII por ser suficiente nos nossos exemplos</p>
</blockquote>
<p>Podemos verificar na tabela ASCII que os códigos hexa de cada caractere da nossa string são:</p>
<pre><code>H: 0x48     
e: 0x65     
l: 0x6C     
l: 0x6C     
o: 0x6F
,: 0x2C
[space]: 0x20
W: 0x57
o: 0x6F
r: 0x72
l: 0x6C
d: 0x64
[newLine]: 0xA
</code></pre>
<blockquote>
<p><strong>0x</strong> é uma notação, um prefixo para determinar que o número depois de <em>x</em> é um hexadecimal</p>
</blockquote>
<p>Com isto podemos então escrever os primeiros bytes hexa do nosso programa:</p>
<pre><code>48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 0A
</code></pre>
<p>Nice, agora vamos "montar" as instruções para a syscall que <em>escreve no STDOUT</em>. Como o kernel faz isso?</p>
<h3>🔵 Syscalls</h3>
<p>Syscalls são <strong>chamadas de sistema</strong> onde o programa sai do modo "user" e entra no modo "kernel". Basicamente, o programa fica à espera que o kernel do sistema operacional execute a função que foi solicitada.</p>
<p>Apesar de <a href="https://x64.syscall.sh/">neste link</a> estar tudo compilado, temos que entender um fator muito importante sobre o kernel que estamos trabalhando, e se trata do <strong>kernel Linux</strong>.</p>
<p>O <a href="https://en.wikipedia.org/wiki/Linux_kernel">kernel Linux</a> foi escrito em C na virada da década de 80 para 90, e por ser escrito em C, todas as chamadas de sistema são declaradas em C.</p>
<p>Por exemplo, no <a href="https://man7.org/linux/man-pages/index.html">manual de system calls</a> do kernel podemos pesquisar sobre qualquer chamada de sistema ou comando utilitário.</p>
<p>A chamada que queremos utilizar pra escrever no STDOUT se chama <a href="https://man7.org/linux/man-pages/man2/write.2.html">write</a>, e é definida pela função:</p>
<pre><code class="language-c">ssize_t write(int fd, const void buf[.count], size_t count);
</code></pre>
<p>...que está presente na biblioteca padrão C (libc), que o kernel incorpora.</p>
<p>Para distribuições GNU, que é o meu caso utilizando Ubuntu, há um repositório mirror do <code>glibc</code> que é a biblioteca padrão em C para sistemas GNU/Linux.</p>
<p>Repara que a função <code>write</code> espera 3 argumentos:</p>
<ul>
<li><code>fd</code>, ou file descriptor, que no nosso caso é o STDOUT, representado pelo valor 1</li>
</ul>
<blockquote>
<p>💡 <strong>STDIN</strong> representa 0 e <strong>STDERR</strong> representa 2. Tá lá no nosso outro artigo sobre Bash e UNIX pipes, corre dar uma olhada</p>
</blockquote>
<ul>
<li>
<p><code>buf</code>, ou buffer, que é o ponteiro para o início do buffer de dados. No caso de escrever "Hello, World" com quebra de linha, o buf apontaria para o início da string "Hello, World\n" na memória.</p>
</li>
<li>
<p><code>count</code>, que é o tamanho do buffer de dados a ser escrito. Para a string "Hello, World\n", o count seria 13, pois isso inclui os 12 caracteres da string mais 1 byte para a quebra de linha.</p>
</li>
</ul>
<h3>🔵 Montando a syscall write</h3>
<p>Para montar uma chamada de sistema, é necessário seguir uma <em>interface</em> que determina como um programa deve comunicar com o sistema operacional.</p>
<p>Quem determina isto é a ABI (Application Binary Interface), que define como estruturas de dados ou funções computacionais conversam entre si.</p>
<p>Como precisamos chamar uma função do kernel Linux, vamos utilizar as <a href="https://en.wikipedia.org/wiki/X86_calling_conventions">convenções determinadas</a> por este sistema operacional e a arquitetura da CPU em questão.</p>
<p>Com isto, podemos montar as instruções utilizando registradores para a syscall <code>write</code>. Novamente seguindo <a href="https://x64.syscall.sh/">esta tabela</a> (que ajuda muito), vamos fazer instrução por instrução:</p>
<p>👉 <strong>ARG0</strong>
O primeiro argumento da função vai no registrador RDI, e aqui vamos colocar o valor <code>1</code> que representa o <em>file descriptor</em> STDOUT. Em hexa, o <a href="http://ref.x86asm.net/coder64.html">manual x86</a> diz que o opcode hexa é <code>BF</code>, seguido do hexa <code>00 00 00 01</code> em formato little-endian:</p>
<pre><code>BF 01 00 00 00
</code></pre>
<p>👉 <strong>ARG1</strong>
O segundo argumento é o ponteiro para o buffer onde começa a string na memória, movido para o registrador RSI.</p>
<p>Como a string fica no começo do programa, geralmente este endereço fica em <code>0x401000</code>. Portanto, o opcode para o <code>move</code> no registrador é <code>48 BE</code> e o valor do endereço de memoria no formato little-endian em hexa, <code>00 10 40</code></p>
<pre><code>48 BE 00 10 40 
</code></pre>
<p>👉 <strong>ARG2</strong>
Já o terceiro argumento da função vai para o registrador RDX, que representa o tamanho do buffer em bytes a ser escrito no file descriptor definido no registrador RDI (ARG0).</p>
<p>O opcode é o <code>BA</code> e o valor é <code>13</code> em hexa, que é <code>00 00 00 0D</code> só que no formato little-endian:</p>
<pre><code>BA 0D 00 00 00
</code></pre>
<p>Agora, vamos colocar no registrador RAX o número da syscall <strong>write</strong>, que de acordo com <a href="https://x64.syscall.sh/">esta tabela</a> (sempre esta tabela, habitue-se a ela), é o número 1, mas em hexa e little-endian:</p>
<pre><code>B8 01 00 00 00
</code></pre>
<blockquote>
<p>Falei bastante desta tabela, de syscalls e little-endian aqui, nos próximos artigos vou falar cada vez menos. A ideia é focar em outras coisas e estes detalhes estarem já bem fundamentados</p>
</blockquote>
<p>Por último, vamos montar a instrução da syscall em si, que é o opcode:</p>
<pre><code>0F 05
</code></pre>
<p>Trecho final da syscall write:</p>
<pre><code>48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 0A ; "Hello, World"

BF 01 00 00 00                 ; MOV 1 para RDI
48 BE 00 10 40                 ; MOV 0x401000 para RSI
BA 0D 00 00 00                 ; MOV 13 para RDX
B8 01 00 00 00                 ; MOV 1 para RAX (write)
0F 05                          ; SYSCALL
</code></pre>
<p>A syscall <code>write</code> já tá montada, agora falta terminar o programa.</p>
<h3>🔵 Montando a syscall exit</h3>
<p>A API da chamada de sistema <strong>exit</strong> pode ser consultada no <a href="https://man7.org/linux/man-pages/man2/_exit.2.html">manual</a>, e tem a seguinte assinatura no <code>glibc</code>:</p>
<pre><code class="language-c">void _exit(int status);
</code></pre>
<p>👉 <strong>ARG0</strong>
O primeiro argumento é o status de término, que de acordo com a especificação <a href="https://en.wikipedia.org/wiki/POSIX">POSIX</a>, pode ser qualquer inteiro de 0 a 255 mas sendo o 0 indicando que o progama terminou sem erros.</p>
<pre><code>BF 00 00 00 00
</code></pre>
<p>Agora, vamos ao trecho final da syscall <em>exit</em>:</p>
<pre><code>BF 00 00 00 00          ; MOV 0 para RDI
B8 3C 00 00 00          ; MOV 60 para RAX (exit)
0F 05                   ; SYSCALL
</code></pre>
<p>Programa completo:</p>
<pre><code>48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 0A
BF 01 00 00 00 
48 BE 00 10 40
BA 0D 00 00 00 
B8 01 00 00 00 
0F 05          
BF 00 00 00 00 
B8 3C 00 00 00 
0F 05          
</code></pre>
<p>Tomando como exemplo o <a href="https://github.com/leandronsp/monica/blob/main/example">binário que disponibilizei</a>, ele tem mais bytes por conta de headers necessários para o próprio sistema operacional admitir o programa.</p>
<p>Vamos executar o binário:</p>
<pre><code class="language-bash">$ ./example

Hello, world
</code></pre>
<p><em>Yay! Que dia, hein?</em></p>
<hr />
<h2>Manipulando o nosso programa</h2>
<p>Com o arquivo binário em mãos, e sugiro baixar do <a href="https://github.com/leandronsp/monica/blob/main/example">repositório</a>, vamos utilizar o utilitário <code>xxd</code> que faz um dump de hexa de qualquer binário, e com ele podemos reparar que o binário vai ter, a partir do byte <em>4096</em>, a mesma quantidade de bytes que escrevemos aqui neste artigo:</p>
<pre><code class="language-bash">xxd -s 4096 -g1 -c8 -l52 example

00001000: 48 65 6c 6c 6f 2c 20 57  Hello, W
00001008: 6f 72 6c 64 0a bf 01 00  orld....
00001010: 00 00 48 be 00 10 40 00  ..H...@.
00001018: 00 00 00 00 ba 0d 00 00  ........
00001020: 00 b8 01 00 00 00 0f 05  ........
00001028: bf 00 00 00 00 b8 3c 00  ......&lt;.
00001030: 00 00 0f 05              ....
</code></pre>
<p><em>Maravilhoso, não?</em></p>
<p>E por fim, antes de concluir este artigo que por si só dá quase uma saga, vamos alterar o binário direto em código de máquina utilizando o utilitário <code>hexedit</code>.</p>
<blockquote>
<p>Esta dica peguei em <a href="https://www.youtube.com/@debxp">vídeos do Blau Araújo</a>. Ele é realmente fantástico e traz conteúdo de primeira. Pra mim é a melhor referência para conteúdo de baixo nível em pt-BR</p>
</blockquote>
<p>Para mudar o binário, rodamos o comando:</p>
<pre><code class="language-bash">hexedit --color example
</code></pre>
<p>Que vai abrir um editor bastante específico. Com <code>/</code>, podemos buscar por um hexa, por exemplo "48", que vai levar para o início da string.</p>
<p>Vamos trocar o "W" maiúsculo por "w" minúsculo, diretamente em código de máquina, que significa trocar o byte <strong>57</strong> da tabela ASCII por <strong>77</strong>:</p>
<p>Então:</p>
<pre><code>48 65 6C 6C  6F 2C 20 57  6F 72 6C 64  0A 
</code></pre>
<p>Passa a ser:</p>
<pre><code>48 65 6C 6C  6F 2C 20 77  6F 72 6C 64  0A 
</code></pre>
<p>Gravar o arquivo com <strong>ctrl+s</strong> e depois <strong>ctrl+c</strong> para sair. Executar novamente e:</p>
<pre><code class="language-bash">$ ./example

Hello, world
</code></pre>
<p><em>MEO DEOS, impressionante!!!!111</em></p>
<hr />
<h2>A vida de quem programa é assim?</h2>
<p>Leitores mais atentos devem estar se perguntando:</p>
<blockquote>
<p>É sempre assim a vida de quem programa? Ficar escovando bits e mudar diretamente código de máquina em hexadecimal?</p>
</blockquote>
<p>Para a maioria esmagadora dos casos, não.</p>
<p>E se a gente criasse um programa tradutor que nos permitisse <em>montar as instruções</em> em uma linguagem mais <em>human-friendly</em> e traduzisse para o código de máquina tal o que vimos aqui neste artigo?</p>
<p>Estamos falando de <strong>assemblers</strong>, que são montadores que permitem escrever em uma linguagem de montagem específica de uma arquitetura (um assembly) e converter para os opcodes, as instruções de CPU.</p>
<p>Reforçando, nesta saga vamos focar no montador NASM para a linguagem Assembly x86 de 64-bits em sistemas GNU/Linux.</p>
<hr />
<h2>Conclusão</h2>
<p>Jornada longa essa, hein? Este artigo cobriu diversos conceitos fundamentais, tais como sistema binário, hexadecimal, opcodes, syscalls, libc, ASCII...</p>
<p>Estes conceitos fundamentam o entendimento para escrever código Assembly, que será o tema principal dos próximos artigos.</p>
<p>No próximo artigo, vamos abordar conceitos básicos de Assembly, buscando converter o simples programa que fizemos neste artigo em um <strong>asm</strong> (atalho pra dizer "assembly") bem organizadinho.</p>
<p>Em seguida, iremos entrar nas syscalls de socket, bind e accept justamente para montarmos o código do nosso web server. Vamos também manipular buffer de arquivos, alocar dados dinamicamente na memória, trabalhar com threads, locks, criar fila na mão, enfim, ainda há muita coisa por vir. Isto aqui é apenas <em>a ponta do iceberg</em>.</p>
<p>Não será um tutorial, mas vamos falar de mnemonics, endereçamento de memória, segmentos de memória, layout de memória de um programa, debugging com <strong>gdb</strong> entre outras coisas.</p>
<p>Fiquem ligades!</p>
<hr />
<h2>Referências</h2>
<sub>
Blau Araújo, material de curso de Assembly
https://codeberg.org/blau_araujo/assembly-nasm-x86_64/raw/branch/main/pdf/aula01.pdf
Felix Cloutier, "x86 instruction reference"
https://www.felixcloutier.com/x86/
ASCII Table
https://upload.wikimedia.org/wikipedia/commons/thumb/1/1b/ASCII-Table-wide.svg/2560px-ASCII-Table-wide.svg.png
POSIX, Wikipedia
https://en.wikipedia.org/wiki/POSIX
ASM x86 Manual
http://ref.x86asm.net/coder64.html
Syscalls table
https://x64.syscall.sh/
Linux Kernel, Wikipedia
https://en.wikipedia.org/wiki/Linux_kernel
ABI, Wikipedia
https://en.wikipedia.org/wiki/Application_binary_interface
x86 calling conventions
https://en.wikipedia.org/wiki/X86_calling_conventions
</sub>
]]></description>
<pubDate>2024-04-23</pubDate>
</item>
<item>
<title>Construindo um web server em Assembly x86, parte II, história e arquitetura</title>
<link>https://leandronsp.com/articles/construindo-um-web-server-em-assembly-x86-parte-ii-historia-e-arquitetura-2jb9.html</link>
<guid>https://leandronsp.com/articles/construindo-um-web-server-em-assembly-x86-parte-ii-historia-e-arquitetura-2jb9.html</guid>
<description><![CDATA[<p>No <a href="https://leandronsp.com/articles/construindo-um-web-server-em-assembly-x86-parte-i-introducao-14p5">artigo anterior</a> demos uma introdução não-técnica sobre o que será esta saga de Assembly x86 conforme avançamos na construção de um web server.</p>
<p>Agora, chegou o momento de começarmos a de fato falar sobre coisas técnicas.</p>
<p>Como de costume, não gosto de esgormitar termos complexos sem a devida explicação. Portanto, vamos iniciar a saga trazendo um pouco de contexto histórico, motivações e porque estamos aqui quando o assunto é <strong>computadores</strong>.</p>
<blockquote>
<p>Okay, agora fui filósofo demais. Mas o que mais importa é que o verdadeiro Assembly são os amigos que fazemos no caminho</p>
</blockquote>
<p>Sem mais delongas, vamos ao que interessa.</p>
<hr />
<h2>Agenda</h2>
<ul>
<li>
<p><a href="#um-pouco-de-hist%C3%B3ria">Um pouco de história</a></p>
</li>
<li>
<p><a href="#computar-informa%C3%A7%C3%B5es">Computar informações</a></p>
<ul>
<li><a href="#m%C3%A1quina-de-turing">Máquina de Turing</a></li>
<li><a href="#arquitetura-de-von-neumann">Arquitetura de von Neumann</a></li>
<li><a href="#o-gargalo-de-von-neumann">O gargalo de von Neumann</a></li>
</ul>
</li>
<li>
<p><a href="#hierarquia-de-mem%C3%B3ria">Hierarquia de memória</a></p>
<ul>
<li><a href="#como-a-cpu-executa-instru%C3%A7%C3%B5es">Como a CPU executa instruções</a></li>
<li><a href="#registradores-de-cpu">Registradores de CPU</a></li>
</ul>
</li>
<li>
<p><a href="#isa">ISA</a></p>
<ul>
<li><a href="#cisc">CISC</a></li>
<li><a href="#risc">RISC</a></li>
</ul>
</li>
<li>
<p><a href="#por-qu%C3%AA-x86">Por quê x86?</a></p>
</li>
<li>
<p><a href="#conclus%C3%A3o">Conclusão</a></p>
</li>
<li>
<p><a href="#refer%C3%AAncias">Referências</a></p>
</li>
</ul>
<hr />
<h2>Um pouco de história</h2>
<p>Ainda muitos milhares de anos a.C, o ser humano precisava realizar cálculos. Um dos instrumentos mais primitivos para esta tarefa era o Ábaco, e certamente você já deve ter visto um:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4rnhwceutwrg1kxc1amx.jpeg" alt="abaco" /></p>
<p>Não vou entrar em detalhes em como um Ábaco funciona, sugiro que compre um e experimente. É divertido. Eu usei quando estava no ensino primário (cria dos anos 90, cof cof).</p>
<h3>Computadores mecânicos</h3>
<p>Ainda nesta "pré-história" dos computadores e já avançando para uma Europa iluminista (circa século XVII), podemos ver a seguir invenções mecânicas e projetos como a máquina de calcular de Blaise Pascal, depois a máquina analítica de Charles Babbage e então a máquina de tabulação de Herman Hollerith.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/96zo1kt2of9iohmx3f02.jpeg" alt="babbage machine" /></p>
<blockquote>
<p>💡 É da máquina de tabulação de Herman Hollerith que vem o nome do seu comprovante de pagamento "holerite"</p>
</blockquote>
<p>Estas máquinas eram mecânicas e tinham muitas limitações como não ter uma memória própria para as "instruções", mas foram muito importantes para a evolução, possibilitando mais tarde que Ada Lovelace pudesse escrever o primeiro possível algoritmo para o projeto da máquina de Babbage.</p>
<blockquote>
<p>Pra quem quiser uma explicação excelente e mais completa sobre a história dos computadores e como estes funcionam, sugiro o vídeo <a href="https://www.youtube.com/watch?v=BbnDmeNojFA">como reinventar um computador do zero</a> do canal Infinitamente.</p>
</blockquote>
<hr />
<h2>Computar informações</h2>
<p>Na era moderna dos computadores, que se dá início no século XX, é quando acontece a revolução eletrônica através das válvulas e dos transistores.</p>
<p>Mas não apenas na área da eletrônica. Foi no século XX que vimos a revolução computacional através de um modelo abstrato que abriu portas para muito do que conhecemos hoje em termos de computadores.</p>
<p>Estamos falando da <strong>máquina de Turing</strong>.</p>
<h3>Máquina de Turing</h3>
<p>Nos anos 30, o matemático Alan Turing desenvolveu o conceito abstrato de uma máquina que possuía uma fita infinita, dividida em células, e um cabeçote de <strong>leitura/escrita</strong> que movia para frente e para trás na fita, possiblitando modificar o estado atual na máquina.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8hpp285hmrzc0txur45m.png" alt="turing machine" /></p>
<p>Basicamente, este modelo de máquina permitiria que certas classes de problemas pudessem ser resolvidas com operações simples.</p>
<p>Extrapolando, nesta fita, que pode representar um tipo de "memória", é possível armazenar o estado mas também outra máquina, ou seja, temos aqui o conceito de uma máquina de Turing universal, que é capaz de simular outra máquina de Turing.</p>
<p>De forma resumida, podemos colocar na fita tanto o estado (dados) quanto as próprias instruções do programa, mitigando assim o problema de limitação que os computadores primitivos tinham, que era resolver problemas complexos de forma mais simples.</p>
<p>Entretanto, a máquina de Turing era apenas uma abstração. Este conceito de instruções e estado na mesma memória precisava ser concretizado.</p>
<p><em>É aí que entra von Neumann</em>.</p>
<h3>Arquitetura de von Neumann</h3>
<p>Von Neumman foi um polímata que propôs um modelo computacional que é utilizado por muitos computadores modernos e dispositivos que usamos hoje em dia.</p>
<p>Neste modelo, temos uma unidade de processamento central, ou CPU, que é responsável por realizar cálculos aritméticos e executar instruções.</p>
<p>Conectada a esta CPU, temos o conceito de <em>memória</em> compartilhada, que vai ser usada para armazenar todas as instruções e estado de um programa de computador.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b0f759rhez1y6kinc7bf.png" alt="von neumann 1" /></p>
<p>Esta arquitetura possibilitou que computadores como ENIAC e EDVAC pudessem ser desenvolvidos. O EDVAC, por sua vez, foi um dos precursores na implementação do modelo de von Neumann, com uma CPU que era conectada a uma memória compartilhada e sequencial.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vmlzomad54zpeapwc2jv.jpeg" alt="edvac" /></p>
<p>À medida que os componentes computacionais foram ficando mais modernos, os computadores foram ficando menores, mais potentes, versáteis e com utilização de propósito mais geral.</p>
<p>Então, a arquitetura de von Neumann pode ainda contar com dispositivos de entrada e saída de dados (impressora, teclado, mouse, placa de rede, monitor, etc), também conhecidos como dispositivos I/O:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fer0xexjvwrt4ooiwbjh.png" alt="von neumann 2" /></p>
<p>Podemos assumir, então, que quando escrevemos um programa de computador, estamos basicamente manipulando uma memória finita (que tem fim) e dispositivos de entrada e saída de dados, através de instruções que são executadas pela CPU.</p>
<p>Tudo graças ao modelo concreto de von Neumann.</p>
<blockquote>
<p>Vale destacar que há arquiteturas computacionais que não seguem este modelo, mas aqui neste guia estamos focando em computadores de propósito geral</p>
</blockquote>
<h3>O gargalo de von Neumann</h3>
<p>Esta arquitetura entretanto traz uma limitação. Como o barramento (caminho) entre a CPU e memória é único, tanto instruções quanto dados trafegam pelo mesmo local, levando a um cenário onde a CPU pode ficar limitada em processamento até que todos os dados sejam lidos do barramento.</p>
<p>Uma forma de mitigar este problema é definir diferentes "níveis" de memória, para que a CPU possa ter uma taxa de processamento maior.</p>
<p>Você acertou, vamos falar agora sobre a <em>hierarquia de memória</em>.</p>
<hr />
<h2>Hierarquia de memória</h2>
<p>Nosso programa manipula memória.</p>
<blockquote>
<p>Com que frequência?</p>
</blockquote>
<p>Todo tempo.</p>
<p>Para mitigar o problema do gargalo de von Neumann, podemos definir uma hierarquia de memória, assim não só a memória principal (RAM) é "enxergada pela CPU" como memória, mas também outros dispositivos de armazenamento no sistema computacional.</p>
<p>Adicionado a isso, com a modernização de computadores no século XX, foi criada a necessidade de orquestrar e controlar todas as interfaces com o hardware. Temos então a concepção de <strong>sistemas operacionais</strong> para esta tarefa, que começam a surgir em meados dos anos 60/70, dentre eles o UNIX.</p>
<p>Ao tratarmos tudo como memória, podemos introduzir tal <strong>hierarquia</strong>. Portanto, num sistema computacional tratamos tudo (ou quase tudo) como memória e assim o sistema operacional (SO) pode abstrair de um determinado programa onde aquilo na hierarquia se encontra de fato sendo utilizado, deixando então nosso programa "livre" deste detalhe de implementação física.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i09gvyk1ext127w3u1mm.png" alt="hierarquia memoria" /></p>
<p>Quanto mais pro topo da hierarquia, menor a capacidade de armazenamento e mais caro. Por exemplo, registradores de  CPU são memórias voláteis que estão no topo.</p>
<blockquote>
<p>Vamos falar sobre registradores mais a seguir na saga</p>
</blockquote>
<p>E quanto mais pra base da hierarquia, maior a capacidade e consequentemente mais barato. Exemplo na base são as unidades de armazenamento durável não-voláteis (HD, SSD etc).</p>
<p>No meio temos a memória principal e volátil, tendo como principal exemplo a memória RAM.</p>
<p>A hierarquia de memória desempenha, então, um papel crucial na forma como a CPU gerencia e acessa memória.</p>
<h3>Como a CPU executa instruções</h3>
<p>Para que uma CPU execute determinada instrução, é necessário ao menos um ciclo de clock, também chamado popularmente como "giro de CPU", ou <em>ciclo de CPU</em>.</p>
<p>Vamos imaginar uma coisa que fica "girando" indefinidamente igual um relógio. De forma simples, é assim que podemos imaginar o clock de uma CPU, como um giro de relógio.</p>
<p>Alguns tipos de instruções podem gastar mais de um ciclo, e determinar quais instruções vão gastar mais ou menos ciclos é algo que é projetado diretamente na <strong>construção da CPU</strong>.</p>
<p>E onde as instruções ficam armazenadas?</p>
<blockquote>
<p>Isso mesmo, na memória.</p>
</blockquote>
<p>Portanto, a CPU precisa buscar a instrução na memória, decodificar, executar e armazenar o resultado de volta na memória.</p>
<p>Tudo isto faz gastar imensos ciclos de CPU. Ao gastarmos ciclos, a CPU pode bater num limite e não conseguir atender a tantas operações no mesmo segundo. Pra não mencionar a latência que a CPU gasta pra utilizar o barramento físico e "viajar" até a memória principal.</p>
<p>Tomando como premissa a hierarquia de memória, e se ao invés de armazenar o resultado na memória principal, a CPU resolver armazenar dentro da própria CPU?</p>
<p>Conheça os <strong>registradores de CPU</strong>.</p>
<h3>Registradores de CPU</h3>
<p>Você já pode estar pensando em cache de CPU, né? Mas calma lá jovem, cache de CPU é outra seara que não pretendo entrar, não por agora.</p>
<blockquote>
<p>Mas que também tem seu lugar na hierarquia de memória</p>
</blockquote>
<p>Lembrando (mais uma vez), da hierarquia de memória, de forma muito simplificada:</p>
<ul>
<li>
<p>topo: registradores</p>
</li>
<li>
<p>depois: cache de CPU</p>
</li>
<li>
<p>ainda depois: memória principal (RAM)</p>
</li>
<li>
<p>beeem depois: memória secundária (HD, SSD)</p>
</li>
</ul>
<p>Quanto mais perto do topo, mais rápido a CPU consegue processar, mas em contrapartida é volátil e também tem menor capacidade de armazenamento.</p>
<p>Então, registradores são apenas memórias de hierarquia mais alta que são preferencialmente usadas para computação porque possuem a menor latência do conjunto de memórias disponíveis.</p>
<p>São nos registradores onde a CPU vai armazenar instruções e dados do programa em execução, de modo a manipular sem precisar ficar dando tantos "saltos" na memória principal, economizando assim latência e ciclos de CPU.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1zzyclx72ff6nyr7b3ag.png" alt="von neumann - registradores" /></p>
<p>Vamos pensar nos registradores como "caixinhas" de tamanho fixo que ficam dentro da CPU.</p>
<p>E como podemos manipular os registradores da CPU? Devemos estabelecer um padrão, que define como controlar o <strong>conjunto de instruções</strong> da CPU.</p>
<p>Precisamos de uma arquitetura de conjunto de instruções, ou <strong>ISA</strong> (<em>Instruction Set Architecture</em>).</p>
<hr />
<h2>ISA</h2>
<p>ISA serve para definir o conjunto instruções e registradores em uma determinada CPU. O fabricante define a ISA, que pode ser classificada de formas diferentes, determinando quantas operações podem ser feitas por instrução, entre outros aspectos.</p>
<p>Neste artigo vamos destacar 2 abordagens de conjunto de instruções: <em>CISC e RISC</em>.</p>
<h3>CISC</h3>
<p>CISC, ou <strong>Complex Instruction Set Computing</strong>, é uma arquitetura onde as instruções podem ser agrupadas em conjuntos mais complexos de instruções, permitindo que uma única instrução execute várias operações complexas.</p>
<p>Aqui, determinadas tarefas podem resultar em apenas um ciclo de CPU, o que aumenta eficiência, mas por outro lado, esta complexidade de instruções pode tornar o tempo de execução menos previsível.</p>
<p>Exemplos de arquiteturas CISC incluem System/360, PDP-11 e Intel 8086.</p>
<h3>RISC</h3>
<p>Para resolver o problema de instruções muito complexas em CISC, a arquiteura RISC, ou <strong>Reduced Instruction Set Computing</strong>, determina uma execução mais simples com menos instruções, diminuindo assim o número de circuitos e consequentemente ciclos de CPU. O tamanho das instruções geralmente é fixo, resultando em um desempenho mais rápido e previsível.</p>
<p>Exemplos de arquiteturas RISC são MIPS e ARM, sendo ARM atualmente utilizada nos processadores de MacBook M1 em diante.</p>
<p>Este é um dos motivos de um MacBook ARM consumir menos bateria e fazer menos barulho, por exemplo.</p>
<h3>E já que o tema é x86...</h3>
<p>Como a saga se trata de x86, especificamente 64-bit (logo mais vamos entender o motivo disso), inicialmente esta arquitetura foi desenvolvida seguindo o padrão CISC, que é o conjunto complexo de instruções.</p>
<p>Entretanto a ISA do x86 foi adaptada para suportar internamente operações simplificadas como encontramos em RISC, portanto pode-se sizer que x86 é um "fake-CISC", que segue um modelo "RISC-ish".</p>
<hr />
<h2>Por quê x86?</h2>
<p>Alguns leitores mais atentos devem estar se perguntando: por quê raios x86? O que isto significa?</p>
<p>Bom, pra entender o que é isto, vamos mergulhar um pouco na história dos microprocessadores da Intel.</p>
<h3>Anos 70</h3>
<p>Os anos 70 foram primordiais. Além da explosão cambriana de sistemas operacionais, vemos também a consolidação da era dos transistores e assim a evolução das CPU's.</p>
<p>Do lado Intel, temos o 8080 de 8-bit lançado em 1974, que além de ser utilizado em sistemas industriais, também foi amplamente encontrado nos primeiros computadores pessoais.</p>
<h3>Intel 8086</h3>
<p>Esta versão traz consigo conjuntos de instruções de 16-bits e foi um marco na era dos computadores pessoais. É aqui que passa a ser cunhado o termo de família "x86", pois o "x" caracteriza qualquer número que venha antes de "86".</p>
<p>Intuitivo, não?</p>
<h3>Anos 80, a década do Intel x86</h3>
<p>A partir de então, nesta década vimos a chegada do 80286 (286) que suporta também 16-bit + 8-bit de endereçamento de memória; depois, o famoso 80386 (i386), que trouxe uma grande mudança suportando instruções de 32-bit e os famosos registradores <code>exx</code> (eax, ebx, eip, etc); para então chegarmos ao 80486 (486), que foi uma melhoria do 386 com suporte a instruções mais avançadas.</p>
<blockquote>
<p>Pra quem tiver curiosidade em saber a especificação da arquitetura x86, está tudo bem documentado num simples <a href="https://software.intel.com/en-us/download/intel-64-and-ia-32-architectures-sdm-combined-volumes-1-2a-2b-2c-2d-3a-3b-3c-3d-and-4">manual de 5000 páginas</a></p>
</blockquote>
<h3>Anos 90, Pentium e além</h3>
<p>O que vem a seguir é a evolução seguindo com os Pentium 586, 686 e depois, uma simplificação dos termos para x86_32 (32-bit) ou x86_64 (64-bit).</p>
<p>Destaca-se os Pentium II, III e os Core "i AlgumaCoisa" que perduram até hoje.</p>
<blockquote>
<p>E os AMD?</p>
</blockquote>
<p>Enquanto a Intel dominava o mercado de CPU's, a AMD (Advanced Micro Devices) mexeu os palitinhos e também lançou versões compatíveis com x86, portanto pode-se dizer que, ao desenvolver para arquitetura x86, é possível executar as mesmas instruções em uma CPU fabricada pela AMD.</p>
<hr />
<h2>Conclusão</h2>
<p>É isto. Este foi um artigo bastante denso, que cobriu uma breve história dos computadores, passando por modelos computacionais até chegar nas arquiteturas de CPU's e entendermos o que significa aquele "x86" no título do artigo.</p>
<p>No próximo artigo, pretendo trazer brevemente sistema binário e hexadecimal para então começar a apresentar aquilo que estamos todos interessados: linguagem de montagem, ou simplesmente <strong>Assembly</strong>.</p>
<p><em>Artigo revisado com carinho por <a href="https://twitter.com/JeffQuesado">Jeff Quesado</a>, o "Coelho da Bolha", e também por <a href="https://twitter.com/_____cadu_____">Cadu</a>, o músico frustrado (same thing).</em></p>
<hr />
<h2>Referências</h2>
<sub>
Ábaco, Wikipedia
https://pt.wikipedia.org/wiki/%C3%81baco
Máquina analítica, Wikipedia
https://pt.wikipedia.org/wiki/M%C3%A1quina_anal%C3%ADtica
Linguagem Assembly, Wikipedia
https://pt.wikipedia.org/wiki/Linguagem_assembly
Cronologia do x86, Wikipedia
https://pt.wikipedia.org/wiki/X86
Arquitetura de Von Neumann, Wikipedia
https://pt.wikipedia.org/wiki/Arquitetura_de_von_Neumann
Blau Araujo, "Fundamentos de Assembly com NASM"
https://codeberg.org/blau_araujo/assembly-nasm-x86_64
História da Computação, Wikipedia
https://pt.wikipedia.org/wiki/Hist%C3%B3ria_da_computa%C3%A7%C3%A3o
Inifitamente, "Como reinventar um computador do zero"
https://www.youtube.com/watch?v=BbnDmeNojFA
Total Phase, "What's a CPU register"
https://www.totalphase.com/blog/2023/05/what-is-register-in-cpu-how-does-it-work/
Turing Machine, Wikipedia
https://en.wikipedia.org/wiki/Turing_machine
Brilliant, "Turing Machines"
https://brilliant.org/wiki/turing-machines/
Instruction Set Architecture, Wikipedia
https://en.wikipedia.org/wiki/Instruction_set_architecture
</sub>
]]></description>
<pubDate>2024-04-11</pubDate>
</item>
<item>
<title>Construindo um web server em Assembly x86, parte I, introdução</title>
<link>https://leandronsp.com/articles/construindo-um-web-server-em-assembly-x86-parte-i-introducao-14p5.html</link>
<guid>https://leandronsp.com/articles/construindo-um-web-server-em-assembly-x86-parte-i-introducao-14p5.html</guid>
<description><![CDATA[<p><em>Assembly</em>.</p>
<p>Para alguns, um monstrinho. Pra outros, algo antiquado. Mas pra quem tem curiosidade em entender como as coisas funcionam, <strong>oportunidade</strong>.</p>
<p>Nesta saga que será uma série de artigos, vamos explorar Assembly com NASM para arquitetura <em>x86-64</em> em GNU/Linux enquanto desenvolvemos um web server multi-thread bastante simples.</p>
<p>O intuito não está nas capacidades do web server em si, pois este irá apenas responder uma mensagem "Hello, world" em formato HTML, mas sim nos conceitos utilizados para construi-lo.</p>
<p>Para quem não tem muita familiaridade com conceitos de mais baixo-nível em computação e concorrência, não se preocupe, pois queremos com este guia fazer com que temas que são vistos como "complexos" possam ser absorvidos com mais facilidade a quem tem curiosidade, podendo então causar aquela boa impressão no churrasco de domingo ou no jantar de Natal.</p>
<blockquote>
<p>To brincando gente, tá proibido falar de Assembly nestas ocasiões, por favor não façam isso eu imploro</p>
</blockquote>
<hr />
<h2>Conteúdo proposto</h2>
<p>Ao longo dos artigos vamos passar por diversos conceitos de computação, sendo alguns de forma superficial e outros com um pouco mais de profundidade, podendo mencionar alguns:</p>
<ul>
<li>
<p>Arquitetura de computadores</p>
</li>
<li>
<p>Tipos de arquiteturas</p>
</li>
<li>
<p>Padrões, sistema binário e hexadecimal</p>
</li>
<li>
<p>Básicos de Assembly x86-64</p>
</li>
<li>
<p>Debugging com GDB</p>
</li>
<li>
<p>Filesystem, Sockets</p>
</li>
<li>
<p>Threads, pool e alocação de memória</p>
</li>
<li>
<p>Arrays, filas, ponteiros</p>
</li>
<li>
<p>Concorrência e primitivos de sincronização como spinlocks e futex</p>
</li>
<li>
<p>Tag <code>h1</code> do HTML (LOL)</p>
</li>
</ul>
<p>Poderá haver mais conceitos a serem abordados, mas que serão mapeados e detalhados conforme necessidade.</p>
<hr />
<h2>É um tutorial de Assembly?</h2>
<p>Definitivamente, não.</p>
<p>A ideia aqui é desenvolver de forma prática um servidor web enquanto passamos por conceitos de Assembly e não só, mas também outros temas importantes sobre concorrência.</p>
<p>Se quer fazer um tutorial rápido para se familiarizar com Assembly x86, há <a href="https://www.tutorialspoint.com/assembly_programming/index.htm">este do tutorials point</a>, mas atenção que ele é para 32-bits, sendo que nosso guia será feito em 64-bits (uma vez entendido os conceitos, fica fácil transportar).</p>
<hr />
<h2>É um curso de Assembly?</h2>
<p><em>Hell no</em>.</p>
<p>Obviamente, vamos aprender diversos conceitos à medida que avançamos na saga, mas se você tem algum interesse em aprender fundamentos de Assembly de forma bastante didática, sugiro acompanhar a playlist de <a href="https://www.youtube.com/live/Ej6U-qk0bdE?feature=shared">Fundamentos de Assembly x86-64</a> do Blau Araújo. Ele é referência no assunto em conteúdo pt-BR e já cortou muito mato neste assunto.</p>
<hr />
<h2>Requisitos de ambiente</h2>
<p>É esperado que o código seja executado em um sistema UNIX-based, preferencialmente GNU/Linux x86_64 que é onde testamos o código proposto.</p>
<ul>
<li>
<p>Sistema Operacional Ubuntu 22.04.4 LTS (GNU/Linux 6.5.0-17-generic x86_64)</p>
</li>
<li>
<p>NASM 2.16.01</p>
</li>
<li>
<p>GNU ld 2.38 (binutils)</p>
</li>
</ul>
<p>E para debugging (claro, nesta casa fazemos debugging, não somos brincalhões):</p>
<ul>
<li>
<p>GNU gdb 12.1</p>
</li>
<li>
<p>strace 5.16</p>
</li>
</ul>
<p>Para outros ambientes, pode-se optar por rodar o código dentro de um container Docker, seguindo as versões acima apresentadas.</p>
<p>Exemplo de <strong>Dockerfile</strong> (validar isto plmdds):</p>
<pre><code class="language-bash">FROM ubuntu
RUN apt-get update &amp;&amp; apt-get -y install make binutils gdb build-essential wget strace
WORKDIR /app
RUN wget https://www.nasm.us/pub/nasm/releasebuilds/2.16.01/nasm-2.16.01.tar.gz -O nasm.tar.gz &amp;&amp; tar -xzvf nasm.tar.gz &amp;&amp; cd nasm-2.16.01 &amp;&amp; ./configure &amp;&amp; make &amp;&amp; make install
</code></pre>
<hr />
<h2>Será que consigo acompanhar?</h2>
<p>Você pode apenas ler e tentar entender os conceitos, ou então ser mais hands-on e escrever código à medida que avança nos artigos.</p>
<p>Recomendo fortemente que experimente o código e rode em seu próprio computador seguindo os requisitos de ambiente, seja em host, virtualizado ou containerizado.</p>
<p>O repositório com o código completo pode ser encontrado em <a href="https://github.com/leandronsp/magali">leandronsp/magali</a>.</p>
<hr />
<p>Este artigo é o primeiro de uma série que vamos explorar sobre Assembly x86-64 enquanto desenvolvemos um web server do zero, sem enrolação e com muita mão na massa.</p>
<p>No próximo artigo já iremos começar com introdução a arquiteturas de computadores e o básico necessário com as terminologias e um pouco de história dos computadores, padrões e linguagens Assembly.</p>
<p>Stay tuned!</p>
]]></description>
<pubDate>2024-04-09</pubDate>
</item>
<item>
<title>Superficial, básico e avançado</title>
<link>https://leandronsp.com/articles/superficial-basico-e-avancado-148n.html</link>
<guid>https://leandronsp.com/articles/superficial-basico-e-avancado-148n.html</guid>
<description><![CDATA[<p>Ufa, finalmente meu primeiro artigo de 2024. Criei <del>vergonha na cara</del> coragem e resolvi sentar e escrever um pouco.</p>
<blockquote>
<p>Vou voltar a escrever coisas técnicas com frequência, prometo</p>
</blockquote>
<p>Mas antes de tudo, queria deixar claro que este <del>bait</del> post aqui é mais uma reflexão do que algo técnico. Não tem referências, não é uma verdade absoluta. Apenas uma reflexão.</p>
<p>Afinal, como dividimos os níveis de conhecimento? Por exemplo, costumamos ver uma divisão entre básico, intermediário e avançado, certo?</p>
<p>Aí é onde entra minha reflexão.</p>
<h2>Um pouco de contexto</h2>
<p>Esses dias estava conversando com uns colegas de trabalho e de repente chegamos nesse tema, sobre o que considerar básico ou avançado.</p>
<p>Pra mim, um dos problemas é que esta divisão pode causar um pouco de ansiedade em algumas pessoas, pois elas se consideram no "básico" e se vêem muito longe do conhecimento que consideram "avançado".</p>
<p>O outro problema advém da régua que estamos nivelando. Ao determinar que tudo se começa pelo básico, e ao vermos que o distanciamento para o avançado é muito grande, isto pode nos deixar presos ao que consideramos básico, portanto, sempre nivelando o conhecimento médio para <em>baixo</em>.</p>
<h2>O básico não é o que parece</h2>
<p>Minha provocação é que o básico não deveria ser considerada a <em>porta de entrada</em> do conhecimento em algum tema.</p>
<p>Ao entrarmos em alguma nova área de conhecimento, estamos de fato em uma <em>superfície</em>, portanto coloco este nível de conhecimento como <em>superficial</em>. Após termos um conhecimento superficial sobre algo, é o momento de entrarmos no básico, o que significa aprofundar um pouco mais neste conhecimento de tal modo que possamos estar <strong>dominando</strong> este conhecimento básico.</p>
<p>Veja, não vamos considerar o <em>domínio em algo</em> como sendo expert ou avançado naquele conhecimento. Não. O domínio do básico deveria ser de tal forma que todos nós buscamos se tivermos algum interesse e, principalmente, se formos atuar com aquele tema no dia-dia.</p>
<h2>Deturparam o básico</h2>
<p>Muito se vê comentarem sobre o problema das pessoas saberem o básico ou então criticarem quando alguém compartilha algum tipo de conteúdo considerado "básico".</p>
<p>Algumas definições de <em>básico</em> no dicionário:</p>
<blockquote>
<p>Que serve como base; essencial, basilar.</p>
</blockquote>
<blockquote>
<p>O mais relevante ou importante de; fundamental.</p>
</blockquote>
<p>Ou seja, o básico tem muito a ver com o <em>fundamental</em>, ou <em>essencial</em>. Ao meu ver, é <em>extremamente importante</em> dominarmos o básico se quisermos atuar com este conhecimento de forma frequente.</p>
<h2>Teu conhecimento é superficial</h2>
<p>Muitas vezes, o que pensamos ser o conhecimento básico em algo é, na verdade, um conhecimento superficial.</p>
<p>Sabemos apenas a superfície. Temos uma noção de como aquilo funciona. Mas não conhecemos profundamente.</p>
<p>Avançar para o básico consiste em aprofundar neste conhecimento. E o básico pode muitas vezes parecer <strong>complexo</strong>.</p>
<p>Mas calma, já explico como dominar o conhecimento básico, e não precisa se desesperar, é necessário apenas um certo grau de dedicação que no fim vai fazer se sentir melhor e mais próximo do que consideramos <em>avançado</em>.</p>
<h2>Domínio do conhecimento básico</h2>
<p>Okay, mas como dominar o conhecimento básico? Aqui entra uma técnica muito sofisticada que consiste em:</p>
<ul>
<li>
<p>fazer tutoriais</p>
</li>
<li>
<p>praticar</p>
</li>
</ul>
<blockquote>
<p>Ah, Leandro. Você tá de BRINCADEIRA COM A MINHA CARA??</p>
</blockquote>
<p>Calma, xofem. Tutoriais são extremamente importantes. Faça tutoriais, um, dois, três, quantos puder. Tire conclusões. Descarte o que não vale a pena.</p>
<p>E depois pratique com aquilo que já sabe. Por exemplo, ao aprender uma nova linguagem de programação, é muito útil escrever um projeto, trecho de código, algoritmo, estrutura de dado, whatever o que quiser que você já tenha feito em outras linguagens.</p>
<p>Isto se chama <em>prática</em>. A prática vai fazer você sair do nível "tutorial" e aprofundar em conceitos básicos, o que naturalmente vai te fazer dominar este nível do conhecimento.</p>
<p>Uma breve analogia (detesto analogias mas aqui vai) é quanto aos níveis de inglês, ou qualquer outro idioma. Muitos pensam que fluência em inglês está no último patamar <em>mas não</em>, a fluência está em todos os níveis: é possível saber apenas o básico do inglês mas buscar fluência dentro do básico.</p>
<p>É sobre isto que estou falando.</p>
<h2>Saindo do superficial</h2>
<p>O lance é que podemos sair do superficial. Este conhecimento de superfície costumo dizer que é como "molhar o pé na piscina". Molhou, a água tá boa? Dá um mergulho, sente a piscina (lá ele com analogia de novo, aff).</p>
<p>O superficial nos dá uma noção, mas sentimos que não dominamos. Sentimos muita dificuldade. Pra mim, muitas dificuldades em algo denunciam um conhecimento superficial, e não o básico.</p>
<p>Ao sair do superficial e buscarmos dominar o básico em algo, estamos subindo a régua, isto nos coloca mais próximos do que consideramos "avançado", mas sem adicionar ansiedade.</p>
<h2>Tá mas o quê é avançado?</h2>
<p>Pra mim, o avançado é <em>basicamente</em> (trocadilho não intencional) se aprofundar ainda mais naquela área do conhecimento. É quando você, apesar de estar confortável com o conhecimento de base e não ficar bloqueado com frequência, quer entrar mais em detalhes e talvez buscar ser ainda mais proficiente naquela área.</p>
<p>Não vou aprofundar em como entrar no avançado, talvez fique pra outro post. Mas aqui meu foco é saindo de superficial e entrando no básico.</p>
<h2>Vamos a exemplos práticos</h2>
<p>Por exemplo, em web, o conhecimento superficial seria saber que eu digito algo no navegador e alguma coisa acontece no servidor. Daí vou olhar o servidor pra ver o que aconteceu. Vejo logs, erros, etc e tento entender esses erros, perguntando, pesquisando e usando outros métodos.</p>
<p>Já o conhecimento básico me coloca num ponto onde sei que por trás desse pedido do navegador, está acontecendo a resolução de nome, roteamento, conexão TCP através da internet, onde do outro lado há uma aplicação rodando em uma porta do sistema operacional atravé de TCP, etc e etc. Este conhecimento me ajuda a aumentar o vocabulário na área e a <em>extrapolar</em> para outros contextos que não web, facilitando na resolução de problemas (troubleshooting).</p>
<p>Já o avançado é quando vou hackear roteadores, usar sysdig para fazer bait de hackers no meu servidor, entre outros aspectos (sigh).</p>
<h2>Outro exemplo prático</h2>
<p>Compiladores. Saber que nossa linguagem é "compilada" para código de máquina é um conhecimento superficial. Conseguimos seguir adiante com este conhecimento? Claro. No dia-dia muitos de nós fazemos CRUD, não é?</p>
<p>Mas o conhecimento básico nos coloca em outro patamar, como entender que está acontecendo a etapa de análise léxica, parsing, otimizações, transformações etc antes de ser gerado de fato o código de máquina. Dominar o básico de compiladores pode ser essencial para identificar pontos de performance em alguma aplicação quando a "água bater no pescoço" em um problema bicudo.</p>
<p>Se você estiver no superficial em compiladores, okay ninguém vai te cobrar nada. Mas imagina fazer parte do grupo que foi lá, arregaçou a manga e fez aquele tweak no interpretador adicionando uma mísera flag apenas porque você sabe <em>o básico de compiladores</em>? Então...</p>
<p>Já o avançado de compiladores considero que seria entender a fundo as otimizações, criar o próprio JIT, fazer patch em runtime, escrever algoritmos de parsing e coisas do gênero.</p>
<p>And so on...</p>
<h2>Conclusão</h2>
<p>Veja, cara pessoa leitora, que não quero causar aqui algum tipo de entropia. Quero apenas que entenda que é possível dominar o básico sem ansiedade, sem toda a carga imposta para saber coisas avançadas em algo.</p>
<p>Ao dominar o básico, um dia você pode encontrar inspiração e vontade para ir mais a fundo no avançado (mas isto não é de todo obrigatório), mas entenda que o conhecimento básico é que nos faz subir no conhecimento, nivelando a régua pra cima, e não pra baixo.</p>
<blockquote>
<p>Mas tudo o que você disse não é o mesmo que básico, intermediário e avançado?</p>
</blockquote>
<p>Interprete como quiser, mas a percepção de estarmos no básico nos afasta do intermediário. Bora sair do básico e ir pro intermediário então, se isto ajudar...rs</p>
<blockquote>
<p>Mas eu não quero saber o básico, quero ficar apenas no superficial</p>
</blockquote>
<p>Tá tudo bem também. É a sua escolha saber o superficial em algo. Se isto não causa nenhuma ansiedade ou te faz mal por não saber coisas mais "avançadas", é perfeitamente aceitável continuar no superficial :)</p>
<p>That's all folks!</p>
]]></description>
<pubDate>2024-03-09</pubDate>
</item>
<item>
<title>Building a dead simple background job in Rust</title>
<link>https://leandronsp.com/articles/building-a-dead-simple-background-job-in-rust-4m1d.html</link>
<guid>https://leandronsp.com/articles/building-a-dead-simple-background-job-in-rust-4m1d.html</guid>
<description><![CDATA[<p>In today's post we'll explore how to create a basic background job in Rust, simulating Rust channels with a Vector-based queue.</p>
<hr />
<h2>First things first</h2>
<p>Generally, a background job operates on one or more threads that continuously consume messages from a queue.</p>
<p>In this post, we'll use a Vector to represent our queue.</p>
<p>This Vector is an instance of the standard Rust library implementation known as <strong>VecDeque</strong>. <a href="https://doc.rust-lang.org/std/collections/struct.VecDeque.html">VecDeque</a> is a double-ended queue that acts as a growing ring buffer.</p>
<h2>Data model</h2>
<p>To make our solution more organized, we can define 3 structs:</p>
<h3>Transmitter</h3>
<p>The transmitter (tx) holds an <strong>store</strong>, which is the queue (Vector) encapsulated by a Arc/Mutex; and an <strong>emitter</strong>, which is a Condvar, used for synchronization based on a condition.</p>
<h3>Receiver</h3>
<p>The receiver (rx), pretty much like the transmitter, also holds a store and an emitter.</p>
<h3>Channel</h3>
<p>Channel holds a transmitter and a receiver.</p>
<pre><code class="language-rust">struct Transmitter&lt;T&gt; {
    store: Arc&lt;Mutex&lt;VecDeque&lt;T&gt;&gt;&gt;,
    emitter: Arc&lt;Condvar&gt;,
}

struct Receiver&lt;T&gt; {
    store: Arc&lt;Mutex&lt;VecDeque&lt;T&gt;&gt;&gt;,
    emitter: Arc&lt;Condvar&gt;,
}

struct Channel&lt;T&gt; {
    tx: Transmitter&lt;T&gt;,
    rx: Receiver&lt;T&gt;,
}
</code></pre>
<h2>What is an Arc in Rust?</h2>
<p>The queue (VecDeque) is going to be shared across the channel for one or more threads.</p>
<p>In Rust, such problem requires shared ownership addressed by a <strong>reference counter</strong> (Rc), but since we are in a multi-thread scenario, Rc is not thread-safe, that's why we need an <em>atomic reference counter</em>, or simply <strong>Arc</strong>, which is indeed thread-safe.</p>
<blockquote>
<p>You can learn more details about smart pointers by reading my post on <a href="https://leandronsp.com/articles/understanding-the-basics-of-smart-pointers-in-rust-3dff">Understanding the basics of smart pointers in Rust</a></p>
</blockquote>
<h2>How about Mutex?</h2>
<p>Since Arc is a reference counter, its references are immutable. For mutability in the underlying data, we need <em>interior mutability</em> using RefCell.</p>
<blockquote>
<p>My mentioned post about smart pointers also covers interior mutability, check it out for further details</p>
</blockquote>
<p>In the same as Rc, <em>RefCell is not thread-safe</em>. For a thread-safe scenario, we need to synchronize access to data using <em>locks</em>. That's where <strong>mutual exclusion</strong> (Mutex) comes in.</p>
<h2>Okay, and Condvar? What the heck is that?</h2>
<p><strong>Condvar</strong> is a primitive for synchronization in concurrent systems where we can put a thread to "wait" (suspended) until a given condition is met.</p>
<p>For blocking queues, we basically want the following condition (pseudo-code):</p>
<pre><code>queue = some_array
mutex = os_lock
emitter = os_condvar

// Thread is suspened until the array gets some data
// There's no CPU consume
while queue is empty
   emitter.wait(mutex)
end

// Someone emitted a signal
data = queue.pop
</code></pre>
<p>In other process:</p>
<pre><code>queue.push(data)
emitter.signal
</code></pre>
<h2>Data modeling implementation</h2>
<p>Now, let's implement the methods <code>send</code> and <code>recv</code> (receive) in our simulated channel.</p>
<h3>Transmitter</h3>
<p>The transmitter (tx) will have a method called <strong>send</strong>, which basically:</p>
<ul>
<li>
<p>locks the shared queue (<code>store.lock().unwrap()</code>)</p>
</li>
<li>
<p>pushes data to the queue (<code>push_back(data)</code>)</p>
</li>
<li>
<p>emits a signal (<code>emitter.notify_one</code>) to notify some suspended thread that is waiting for data in the queue</p>
</li>
</ul>
<pre><code class="language-rust">impl&lt;T&gt; Transmitter&lt;T&gt; {
    fn send(&amp;self, data: T) {
        self.store.lock().unwrap().push_back(data);
        self.emitter.notify_one();
    }
}
</code></pre>
<h3>Receiver</h3>
<p>The receiver (rx) has a method called <strong>recv</strong> (short for <em>receive</em>) which:</p>
<ul>
<li>
<p>creates a lock in the shared queue (<code>store.lock().unwrap()</code>)</p>
</li>
<li>
<p>suspends the current thread until the condition is met, in other words, <strong>while the queue is empty</strong>, the thread is suspended in the operating system, thus not consuming CPU (<code>emitter.wait</code>)</p>
</li>
<li>
<p>once the thread is awaken, it can pops the data from the queue (<code>store.pop_front()</code>)</p>
</li>
</ul>
<pre><code class="language-rust">impl&lt;T&gt; Receiver&lt;T&gt; {
    fn recv(&amp;self) -&gt; Option&lt;T&gt; {
        let mut store = self.store.lock().unwrap();

        while store.is_empty() {
            store = self.emitter.wait(store).unwrap();
        }

        store.pop_front()
    }
}
</code></pre>
<p>Moreover, the Receiver struct can have an extra method called <strong>try_recv</strong> which does not block the thread, not using the Condvar condition:</p>
<pre><code class="language-rust">fn try_recv(&amp;self) -&gt; Option&lt;T&gt; {
    self.store.lock().unwrap().pop_front()
}
</code></pre>
<h3>Channel</h3>
<p>Once the Transmitter and Receiver are already implemented, the implementation of Channel is a piece of cake:</p>
<pre><code class="language-rust">impl&lt;T&gt; Channel&lt;T&gt; {
    fn new() -&gt; Self {
        let store = Arc::new(Mutex::new(VecDeque::new()));
        let emitter = Arc::new(Condvar::new());

        Channel {
            tx: Transmitter { store: Arc::clone(&amp;store), emitter: Arc::clone(&amp;emitter) },
            rx: Receiver { store: Arc::clone(&amp;store), emitter: Arc::clone(&amp;emitter) },
        }
    }
}
</code></pre>
<p>Note that both Mutex and Condvar are encapsulated in an Arc (atomic reference counter), because we have to share them across tx and rx at the same time.</p>
<h3>Main</h3>
<p>The <strong>main</strong> function can me implemented as follows:</p>
<ul>
<li>
<p>create a channel and binds the <strong>tx</strong> and <strong>rx</strong> respectively</p>
</li>
<li>
<p>the channel holds a shared Mutex/VecDeque and a Condvar</p>
</li>
<li>
<p>tx is used to send data to the channel</p>
</li>
<li>
<p>rx is used from the inner thread to receive data from the channel</p>
</li>
</ul>
<pre><code class="language-rust">fn main() {
    // Initialize channel
    let channel = Channel::new();
    let (tx, rx) = (channel.tx, channel.rx);

    // Push data to the channel
    tx.send("Some job to do: 1");
    tx.send("Another job: 2");

    // Process the channel
    let worker = thread::spawn(move || {
        loop {
            let job = rx.recv(); // we could use try_recv too

            match job {
                Some(job) =&gt; println!("Job: {}", job),
                None =&gt; break,
            }
        }
    });
    
    // Push more data to the channel
    tx.send("Yet another job");

    worker.join().unwrap();
}
</code></pre>
<p>We run the code and, <strong>Yay</strong>, everything is working as expected:</p>
<pre><code>Job: Some job to do: 1
Job: Another job: 2
Job: Yet another job
</code></pre>
<hr />
<h2>Rust channels for the rescue</h2>
<p>You may be wondering:</p>
<blockquote>
<p>Hey Leandro, why doesn't Rust bring all this stuff already built-in? Do we really need to implement a raw queue and use synchronization primitives on our own every time we want to create a channel for threads?</p>
</blockquote>
<p><em>Today is your lucky day</em>. Indeed Rust brings <strong>Channels</strong>, which employ the same techniques described in this very post, but more robust, of course:</p>
<pre><code class="language-rust">use std::sync::mpsc;
use std::thread;

fn main() {
    // Initialize channel
    let (tx, rx) = mpsc::channel();

    // Push data to the channel
    tx.send("Some job to do: 1").unwrap();
    tx.send("Another job: 2").unwrap();

    let worker = thread::spawn(move || {
        loop {
            let job = rx.recv();

            match job {
                Ok(job) =&gt; println!("Job: {}", job),
                Err(_) =&gt; break,
            }
        }
    });

    // Push more data to the channel
    tx.send("Yet another job").unwrap();

    worker.join().unwrap();
}
</code></pre>
<ul>
<li>
<p><code>mpsc</code> stands for <strong>multiple producers, single consumer</strong></p>
</li>
<li>
<p><code>mpsc::channel</code> creates a channel with a internal shared queue and returns a transmitter (tx) and a receiver (rx)</p>
</li>
<li>
<p>pretty much like our custom implementation, <code>tx.send</code> sends data to the channel, whereas <code>tx.recv</code> reads/pops data from the channel</p>
</li>
</ul>
<p><em>How cool is that</em>?</p>
<hr />
<h2>References</h2>
<p>https://doc.rust-lang.org/book/ch16-00-concurrency.html</p>
<p>https://doc.rust-lang.org/std/vec/struct.Vec.html</p>
<p>https://doc.rust-lang.org/std/collections/struct.VecDeque.html</p>
<p>https://leandronsp.com/articles/understanding-the-basics-of-smart-pointers-in-rust-3dff</p>
]]></description>
<pubDate>2023-11-12</pubDate>
</item>
<item>
<title>Understanding the basics of Smart Pointers in Rust</title>
<link>https://leandronsp.com/articles/understanding-the-basics-of-smart-pointers-in-rust-3dff.html</link>
<guid>https://leandronsp.com/articles/understanding-the-basics-of-smart-pointers-in-rust-3dff.html</guid>
<description><![CDATA[<p>In today's post we'll delve into the basics of smart pointers in Rust, while we build from scratch a simple linked list - starting from a singly linked list and then evolving to a doubly one.</p>
<hr />
<h2>Prelude, intro to Rust</h2>
<p>It's not intended to be an introduction about Rust. For that, you can follow along <a href="https://dev.to/mfcastellani/series/23318">this blogpost series</a> by <a href="https://dev.to/mfcastellani">@mfcastellani</a>.</p>
<p>Also, you can read <a href="https://www.casadocodigo.com.br/products/livro-rust">his book</a> (pt-BR). Moreover, I have a <a href="https://www.youtube.com/watch?v=6VSgMbFNUuQ">live coding video</a> where I explored the Rust fundamentals by covering an introduction to Rust, data types, functions, ownership, references, structs/enums and error handling.</p>
<p>Another content about Rust I higly recommend is presented on <a href="https://www.youtube.com/watch?v=zWXloY0sslE">this Youtube channel</a> by Bruno Rocha, which creates great videos about Rust as well (pt-BR).</p>
<blockquote>
<p>Please note that this post you are currently reading was written during a <a href="https://www.youtube.com/watch?v=bdZe0LjDUyk">live coding session (pt-BR)</a> where you can follow the process I use to write blogposts in general and how I created this particular one. It's a novel format I'm experimenting with to share content.</p>
</blockquote>
<p>However, if you are looking for introdutory content in english only, the Youtube channel <a href="https://www.youtube.com/@letsgetrusty">Let's get Rusty</a> provides great content on Rust from basics to advanced.</p>
<hr />
<p>No more introduction, let's embark on this journey of <strong>Smart Pointers in Rust</strong>.</p>
<hr />
<h2>Table of Contents</h2>
<ul>
<li>
<p><a href="#first-things-first">First things first</a></p>
</li>
<li>
<p><a href="#a-linked-list-using-rust">A linked list using Rust</a></p>
</li>
<li>
<p><a href="#box">Meet the Box smart pointer</a></p>
</li>
<li>
<p><a href="#rc">Shared ownership using Rc</a></p>
</li>
<li>
<p><a href="#refcell">Interior mutability with RefCell</a></p>
</li>
<li>
<p><a href="#thinking-about-a-circular-linked-list">Weak references on a circular linked list</a></p>
</li>
</ul>
<hr />
<h2>👉 First things first</h2>
<p>Rust employs a mechanism for dealing with memory management where it prevents dangling references, double free error and other problems related to memory management.</p>
<p>This mechanism is called "ownership" and through <a href="https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization">RAII</a> (Resource Acquisition Is Initialization), it follows three basic rules:</p>
<ul>
<li>
<p>Each value in Rust has a single owner</p>
</li>
<li>
<p>There are only <em>one</em> owner at a time</p>
</li>
<li>
<p>When the owner's scope is finished, its associated value is dropped and invalidated</p>
</li>
</ul>
<p>When we need to transfer ownership, in case the value is in the stack (fixed-sized types), Rust performs a <em>Copy</em>:</p>
<blockquote>
<p>I'm assuming that all code snippets within this post are being executed inside a <code>fn main() {}</code> function</p>
</blockquote>
<pre><code class="language-rust">let age = 20;
let copied_age = age;

println!("copied_age: {}", copied_age);
println!("age: {}", age); // age is still valid because Rust performs a "Copy" for data in the stack
</code></pre>
<p>As for <em>dynamically-sized</em> types, which live in the heap, Rust performs a <em>Move</em>:</p>
<pre><code class="language-rust">let name = String::from("John");
let other_name = name;

println!("other_name: {}", other_name);
println!("name: {}", name); // name is no loger valid because Rust performs a "Move"

// Error:
// error[E0382]: borrow of moved value: `name`
</code></pre>
<p><em>Copy</em> literally copies the data in the stack, while the <em>Move</em> operation transfers ownership, which means that the former owner is no longer the owner and its reference is completely dropped.</p>
<hr />
<h2>👉 A Linked List using Rust</h2>
<p>A linked list is a data structure which represents a collection of nodes where each node points to the next node. This is basically a <strong>singly linked list</strong>.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n2et97dstkxf8y265rsv.png" alt="a singly linked list" /></p>
<p>Also, we can build a linked list where each node points to the previous node as well. In this case, such a list is called <strong>doubly linked list</strong>.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eq1gutt2osbn8vwiv5ew.png" alt="a doubly linked list" /></p>
<h3>🔵 A Singly Linked List</h3>
<p>The first version of our linked list will be a singly one. As we evolve to a doubly linked list, we'll bring Rust concepts about ownership, references and smart pointers.</p>
<p>We start by modeling the Node:</p>
<pre><code class="language-rust">struct Node {
    value: i32,
    next: Node
}
</code></pre>
<p>We are bound to situations where the <strong>next</strong> pointer points to "nothing", or simply a <code>null</code> pointer when the list reaches the end, commonly seen in a variety of programming languages.</p>
<p>But Rust has no <code>null</code> pointers. That said, we can represent the <code>next</code> pointer by using the enum <strong>Option</strong>, which in Rust gives us two possibilities of types:</p>
<ul>
<li>
<p>None (the end of the list)</p>
</li>
<li>
<p>Some(node)</p>
</li>
</ul>
<pre><code class="language-rust">struct Node {
    value: i32,
    next: Option&lt;Node&gt;
}

let head = Node { value: 1, next: None };
assert_eq!(1, head.value);
assert_eq!(None, head.next);
</code></pre>
<p>The above code is not yet compiling:</p>
<pre><code>error[E0072]: recursive type `Node` has infinite size
 --&gt; src/main.rs:2:5
  |
2 |     struct Node {
  |     ^^^^^^^^^^^
3 |         value: i32,
4 |         next: Option&lt;Node&gt;
  |                      ---- recursive without indirection
  |
help: insert some indirection (e.g., a `Box`, `Rc`, or `&amp;`) to break the cycle
  |
4 |         next: Option&lt;Box&lt;Node&gt;&gt;
  |                      ++++    +
</code></pre>
<p>The Rust compiler is saying that <em>Node</em> has unknown size at compile-time and as such it can't be determined, because the "next" pointer points to another Node which points to another Node and so on, infinitely.</p>
<p>This is a <strong>recursive type</strong>.</p>
<p>In order to solve this problem, we have to help the Rust compiler to use some abstraction which can allocate data on the heap and determine the size of the Node at compile-time, resolving the recursive type.</p>
<p>Such abstraction is called <strong>Box</strong>, which is a smart pointer in Rust.</p>
<hr />
<h2>👉 Box</h2>
<p>By using Box, we want to allocate the data on the heap.</p>
<p>Also, Box has a known size at compile-time. Being a pointer, the <em>size of the Box is the pointer size</em>, which makes it a good fit for recursive types.</p>
<p>The following code compiles sucessfully:</p>
<pre><code class="language-rust">#[derive(Debug, PartialEq)]
struct Node {
    value: i32,
    next: Option&lt;Box&lt;Node&gt;&gt;
}

let head = Node { value: 1, next: None };

assert_eq!(1, head.value);
assert_eq!(None, head.next);
</code></pre>
<p>What if we add one more node, called "tail"?</p>
<pre><code class="language-rust">let tail = Node { value: 2 next: None };
let head = Node { value: 1, next: Some(tail) };
</code></pre>
<p>As always (the Rust compilers always wins), it won't compile:</p>
<pre><code>---- ^^^^ expected `Box&lt;Node&gt;`, found `Node`
</code></pre>
<p>We have to wrap the tail in a <em>Box</em>:</p>
<pre><code class="language-rust">struct Node {
    value: i32,
    next: Option&lt;Box&lt;Node&gt;&gt;
}

let tail = Box::new(Node { value: 2, next: None });
let head = Node { value: 1, next: Some(tail) };

assert_eq!(1, head.value);
assert_eq!(2, head.next.unwrap().value);
</code></pre>
<ul>
<li>
<p>We wrap the tail box in an Option (Some)</p>
</li>
<li>
<p>The <em>head.next</em> points to an <strong>Option</strong>. Because it's the enum Option, we have to call <code>unwrap</code> to fetch the underlying value</p>
</li>
</ul>
<p>Let's go further in the example and implement a <strong>doubly linked list</strong>, by specifying the <em>prev</em> attribute on the Node struct.</p>
<h3>🔵 A Doubly Linked List</h3>
<pre><code class="language-rust">struct Node {
    value: i32,
    next: Option&lt;Box&lt;Node&gt;&gt;,
    prev: Option&lt;Box&lt;Node&gt;&gt;,
}

let tail = Box::new(Node { value: 2, prev: None, next: None });
let head = Node { value: 1, prev: None, next: Some(tail) };
</code></pre>
<ul>
<li>
<p>the <code>head.prev</code> points to <code>None</code></p>
</li>
<li>
<p>the <code>tail.prev</code> points to <code>None</code> (at this moment...)</p>
</li>
</ul>
<p>In order to change the <code>tail.prev</code>, we have to mutate its underlying value, from <code>None</code> to <code>Some(head)</code>. May we change the source code:</p>
<pre><code class="language-rust">let mut tail = Box::new(Node { value: 2, prev: None, next: None });
let head = Box::new(Node { value: 1, prev: None, next: Some(tail) });

tail.prev = Some(head); // mutating the tail.prev
</code></pre>
<p>And...</p>
<pre><code>error[E0382]: use of moved value: `head.next`
  --&gt; src/main.rs:14:15
   |
9  | let head = Box::new(Node { value: 1, prev: None, next: Some(tail) });
   |     ---- move occurs because `head` has type `Box&lt;Node&gt;`, which does not implement the `Copy` trait
10 |
11 | tail.prev = Some(head);
   |                  ---- value moved here
...
14 | assert_eq!(2, head.next.unwrap().value);
   |               ^^^^^^^^^ value used here after move
</code></pre>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/znv09sx9a23qp53f4p3k.gif" alt="a very very explosion" /></p>
<p><em>Welcome to the ownership saga in Rust!</em></p>
<p>Let's clarify some points here:</p>
<p>First, a Box has <strong>single ownership</strong>, meaning that each value holds one owner at a time. Here, in this line:</p>
<pre><code class="language-rust">let head = Box::new(Node { value: 1, prev: None, next: Some(tail) }); // value was moved here
</code></pre>
<p><code>Tail</code> has been <em>moved</em>, that's why we cannot use it later, due to ownership rules.</p>
<p>To fix that, we can make use of the method <code>clone</code> implemented in the Box, which will perform a deep copy (clone) of the value in the heap:</p>
<pre><code class="language-rust">let head = Box::new(Node { value: 1, prev: None, next: Some(tail.clone()) });

tail.prev = Some(head);
</code></pre>
<p>Additionally, in the following line, <code>tail.prev</code> takes ownership of the value of <code>head</code>, so the value was moved to the new owner:</p>
<pre><code class="language-rust">tail.prev = Some(head); // value as moved here
</code></pre>
<p>Now the solution is calling <code>clone</code> as we did in the <code>tail</code>:</p>
<pre><code class="language-rust">tail.prev = Some(head.clone());
</code></pre>
<p>Here's the current solution for a doubly linked list using Box:</p>
<pre><code class="language-rust">#[derive(Clone)]
struct Node {
    value: i32,
    next: Option&lt;Box&lt;Node&gt;&gt;,
    prev: Option&lt;Box&lt;Node&gt;&gt;,
}

let mut tail = Box::new(Node { value: 2, prev: None, next: None });
let head = Box::new(Node { value: 1, prev: None, next: Some(tail.clone()) });

tail.prev = Some(head.clone());

assert_eq!(1, head.value);
assert_eq!(2, tail.value);
assert_eq!(2, head.next.unwrap().value);
assert_eq!(1, tail.prev.unwrap().value);
</code></pre>
<p>By using Box, we've solved the problem but we may end up wasting memory, as demonstrated in the following picture:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aj2tz2i7kcpq0cdjzmjb.png" alt="box wasting memory on linked list" /></p>
<p>At this point in time, we have the following abstraction model about ownership, which is single and shares no value in the heap (Box):</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6fkmdx1czv6jlulzwqtj.png" alt="box single ownership" /></p>
<p>We have to find a way to overcome the single ownership problem. What about <em>not taking ownership at all</em>, by using <strong>References</strong> instead?</p>
<h3>🔵 References &amp; Lifetimes</h3>
<p>References in Rust do not take ownership, as they allow to work with the reference of the data which is allocated in the heap.</p>
<p>This way, references can be "borrowed" without taking ownership, and as such they are bound to a mechanism called <strong>borrow checker</strong>.</p>
<pre><code class="language-rust">let name = String::from("John"); // value in the heap. name is the owner
let other_name = &amp;name; // not a move. other_name has a reference to the value in the heap. name is still the owner

println!("other_name: {}", other_name);
println!("name: {}", name);
</code></pre>
<p>The above code compiles successfully. The borrow checker ensures that the reference is pointing to some valid value in the heap, thus not "moving" the ownership.</p>
<p>Let's change the code to use References instead of Box:</p>
<pre><code class="language-rust">struct Node {
    value: i32,
    next: Option&lt;&amp;Node&gt;,
}

let tail = Node { value: 2, next: None };
let head = Node { value: 1, next: Some(&amp;tail) };
</code></pre>
<ul>
<li>
<p>The <code>next</code> is an enum Option which wraps a <em>reference to another Node</em></p>
</li>
<li>
<p>The <code>head.next</code> is now using <code>Some(&amp;tail)</code> which is a reference to the tail (other node), instead of a Box which takes ownership</p>
</li>
</ul>
<p>But this code won't compile yet:</p>
<pre><code>error[E0106]: missing lifetime specifier
 --&gt; src/main.rs:4:18
  |
4 |     next: Option&lt;&amp;Node&gt;,
  |                  ^ expected named lifetime parameter
  |
help: consider introducing a named lifetime parameter
  |
2 ~ struct Node&lt;'a&gt; {
3 |     value: i32,
4 ~     next: Option&lt;&amp;'a Node&gt;,
</code></pre>
<p>Each reference has an implicit lifetime in the Rust compiler. In our example of a linked list, the compiler can't determine the lifetime of the <code>next</code> pointer because it points to another Node which could have a different lifetime.</p>
<p>Because the borrow checker prevents dangling references by using lifetimes, we have to help the compiler by annotating lifetimes in the struct definition:</p>
<pre><code class="language-rust">struct Node&lt;'a&gt; {
    value: i32,
    next: Option&lt;&amp;'a Node&lt;'a&gt;&gt;,
}

// or, using generics

struct Node&lt;'a, T&gt; {
    value: T,
    next: Option&lt;&amp;'a Node&lt;'a, T&gt;&gt;,
}
</code></pre>
<p><em>It's quite verbose, I know.</em> 😬</p>
<p>Now the version of a singly linked list using references:</p>
<pre><code class="language-rust">#[derive(Debug, PartialEq)]
struct Node&lt;'a, T&gt; {
    value: T,
    next: Option&lt;&amp;'a Node&lt;'a, T&gt;&gt;,
}

let tail = Node { value: 2, next: None };
let head = Node { value: 1, next: Some(&amp;tail) };

assert_eq!(1, head.value);
assert_eq!(2, head.next.unwrap().value);
assert_eq!(None, tail.next);
</code></pre>
<ul>
<li>
<p>The <code>Node</code> and its <code>next</code> (reference) node has a lifetime <code>'a</code></p>
</li>
<li>
<p>we can use tail/head even after they been applied to the repective nodes, because we took no ownership</p>
</li>
</ul>
<p>But a singly linked list is not enough. We want a doubly one:</p>
<pre><code class="language-rust">#[derive(Debug, PartialEq)]
struct Node&lt;'a, T&gt; {
    value: T,
    next: Option&lt;&amp;'a Node&lt;'a, T&gt;&gt;,
    prev: Option&lt;&amp;'a Node&lt;'a, T&gt;&gt;,
}

let mut tail = Node { value: 2, prev: None, next: None };
let head = Node { value: 1, prev: None, next: Some(&amp;tail) };

tail.prev = Some(&amp;head);

assert_eq!(1, head.value);
assert_eq!(2, head.next.unwrap().value);
assert_eq!(None, tail.next);
</code></pre>
<p>We run the code and...</p>
<pre><code>error[E0506]: cannot assign to `tail.prev` because it is borrowed
  --&gt; src/main.rs:12:1
   |
10 | let head = Node { value: 1, prev: None, next: Some(&amp;tail) };
   |                                                    ----- `tail.prev` is borrowed here
11 |
12 | tail.prev = Some(&amp;head);
   | ^^^^^^^^^^^^^^^^^^^^^^^ `tail.prev` is assigned to here but it was already borrowed
13 |
14 | assert_eq!(1, head.value);
   | ------------------------- borrow later used here
</code></pre>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/khry9qxk07s1com4hsik.gif" alt="yet another explosion" /></p>
<p><em>What happened here?</em></p>
<p>The <strong>borrow checker</strong> checks at compile-time that we can have only <em>one mutable reference</em> at a time in the same scope.</p>
<p>Our example has a scenario where the <code>tail.prev</code> is <strong>mutable</strong> and is already borrowed to the <code>head</code>.</p>
<p>That's why we simply <em>can't implement a doubly linked list</em> in Rust using references (AFAIK).</p>
<p>Then we should go back to ownership. But what about having a "shared ownership" instead of a "single ownership" like in the Box example?</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p2l5w1f09rr5oyx3bsk0.png" alt="ideal scenario for shared ownerhsip" /></p>
<p>Enter <em>Rc</em>.</p>
<hr />
<h2>👉 Rc</h2>
<p>Rc stands for <strong>reference counting</strong>, which performs heap allocation, like a Box.</p>
<p>But unlike Box, it enables <em>shared ownership</em>, where one or more owners point to the same value in the heap. Each time an owner <em>comes to the party</em>, it increments the counter. When the owner goes out of scope, it decrements the counter.</p>
<p>Only when all owners are dropped, then the Rc is entirely dropped as well freeing the underlying data from the heap.</p>
<p>Rc brings one caveat: <strong>the reference must be immutable</strong>. Otherwise, it would lead to double-free errors.</p>
<pre><code class="language-rust">use std::rc::Rc;
let name = Rc::new(String::from("John"));

assert_eq!(1, Rc::strong_count(&amp;name));

let cloned_name = Rc::clone(&amp;name);

assert_eq!(2, Rc::strong_count(&amp;name));
assert_eq!("John", *cloned_name); // Dereference
assert_eq!("John", *name); // Dereference
</code></pre>
<p>Each time an <code>Rc</code> is called <code>data.clone()</code> or by using <code>Rc::clone(&amp;data)</code>, the data is not being copied on the heap (deep copy). Only the reference is copied and the strong count of references is incremented.</p>
<p>The original owner is still valid after <em>cloning</em> multiple Rc references.</p>
<p>Let's implement the singly linked list using Rc instead of references or Box:</p>
<pre><code class="language-rust">use std::rc::Rc;

struct Node&lt;T&gt; {
    value: T,
    next: Option&lt;Rc&lt;Node&lt;T&gt;&gt;&gt;
}
</code></pre>
<p>Cool, now let's add some data to our linked list:</p>
<pre><code class="language-rust">let tail = Rc::new(Node { value: 2, next: None });
let head = Rc::new(Node { value: 1, next: Some(tail) });

assert_eq!(1, head.value);
assert_eq!(2, head.next.clone().unwrap().value);
</code></pre>
<p>It simply works! <em>How cool is that?</em></p>
<p>Time to evolve to a doubly linked list using Rc:</p>
<pre><code class="language-rust">use std::rc::Rc;

struct Node&lt;T&gt; {
    value: T,
    next: Option&lt;Rc&lt;Node&lt;T&gt;&gt;&gt;,
    prev: Option&lt;Rc&lt;Node&lt;T&gt;&gt;&gt;,
}

let tail = Rc::new(Node { value: 2, prev: None, next: None });
let head = Rc::new(Node { value: 1, prev: None, next: Some(Rc::clone(&amp;tail)) });

tail.prev = Some(Rc::clone(&amp;head));

assert_eq!(1, head.value);
assert_eq!(2, head.next.clone().unwrap().value);
</code></pre>
<p>Instead of deep copy like in Box, the Rc smart pointer only increments the reference counter. Check <code>Rc::clone(&amp;head)</code> and <code>Rc::clone(&amp;tail)</code>.</p>
<p>But it won't compile:</p>
<pre><code>error[E0594]: cannot assign to data in an `Rc`
  --&gt; src/main.rs:24:5
   |
24 |     tail.prev = Some(Rc::clone(&amp;head));
   |     ^^^^^^^^^ cannot assign
</code></pre>
<p><em>Cannot assign data in an Rc!</em></p>
<p>Even if we used <code>let mut tail = ...</code>, Rc is now allowed to mutate because <strong>all references in Rc are immutable</strong>.</p>
<p>How about <em>mutating the underlying data</em> even if the reference is immutable? We could achieve that by using "unsafe Rust", where <strong>some checks could be done at runtime instead of compile-time.</strong></p>
<p>Even better, what about Rust providing an abstraction which uses unsafe capabilities under the hood but wrapping in a safe API?</p>
<p>Yes, <em>we are talking about RefCell</em>.</p>
<hr />
<h2>👉 RefCell</h2>
<p><strong>RefCell</strong> is an smart pointer which provides a safe API to mutate underlying data (on the heap) but through immutable references.</p>
<p>This approach is called <strong>interior mutability</strong>.</p>
<p>The borrow checker won't perform checks, but Rust will check them at runtime. In case we cause a problem regarding mutable data, the program will crash and stop (<code>panic!</code>).</p>
<pre><code class="language-rust">use std::cell::RefCell;

let name = RefCell::new(String::from("John"));
name.borrow_mut().push_str(" Doe");

assert_eq!("John Doe", *name.borrow());
</code></pre>
<ul>
<li>
<p>RefCell wraps a String in the heap</p>
</li>
<li>
<p>The reference is immutable</p>
</li>
<li>
<p>Through <code>borrow_mut</code>, we get <code>RefMut&lt;T&gt;</code> to mutate the underlying data</p>
</li>
<li>
<p>Through <code>borrow</code>, we get a <code>Ref&lt;T&gt;</code> to read the underlying data</p>
</li>
</ul>
<p>In a RefCell, we can have multiple borrows for reading or <strong>only one borrow mutable</strong> for writing.</p>
<p>With that in place, time to implement our doubly linked list using Rc + RefCell:</p>
<pre><code class="language-rust">    use std::rc::Rc;
    use std::cell::RefCell;

    struct Node&lt;T&gt; {
        value: T,
        next: Option&lt;Rc&lt;RefCell&lt;Node&lt;T&gt;&gt;&gt;&gt;,
        prev: Option&lt;Rc&lt;RefCell&lt;Node&lt;T&gt;&gt;&gt;&gt;,
    }

    let tail = Rc::new(RefCell::new(Node { value: 2, prev: None, next: None }));
    let head = Rc::new(RefCell::new(Node { value: 1, prev: None, next: Some(Rc::clone(&amp;tail)) }));

    tail.borrow_mut().prev = Some(Rc::clone(&amp;head));

    assert_eq!(1, head.borrow().value);
    assert_eq!(2, head.borrow().next.clone().unwrap().borrow().value);
    assert_eq!(1, tail.borrow().prev.clone().unwrap().borrow().value);
</code></pre>
<p>Our Node model now is composed of a value and a <code>next</code> pointer which basically is:</p>
<ul>
<li>
<p>an enum Option</p>
</li>
<li>
<p>which wraps an Rc (shared ownership)</p>
</li>
<li>
<p>which wraps an RefCell (for interior mutability)</p>
</li>
<li>
<p>which points to other Node</p>
</li>
<li>
<p>and so on and on and on...</p>
</li>
</ul>
<p>With RefCell, every time we have to write, we use <code>borrow_mut</code>, and every time we have to read, we use <code>borrow</code>.</p>
<p><em>How wonderful is that?</em></p>
<hr />
<h2>👉 Thinking about a circular linked list</h2>
<p>In order to make our linked list to be circular, we have to make <code>tail.next</code> point to the <code>head</code>:</p>
<pre><code class="language-rust">use std::rc::Rc;
use std::cell::RefCell;

struct Node&lt;T&gt; {
    value: T,
    next: Option&lt;Rc&lt;RefCell&lt;Node&lt;T&gt;&gt;&gt;&gt;,
    prev: Option&lt;Rc&lt;RefCell&lt;Node&lt;T&gt;&gt;&gt;&gt;,
}

let tail = Rc::new(RefCell::new(Node { value: 2, prev: None, next: None }));
let head = Rc::new(RefCell::new(Node { value: 1, prev: None, next: Some(Rc::clone(&amp;tail)) }));

tail.borrow_mut().prev = Some(Rc::clone(&amp;head));
tail.borrow_mut().next = Some(Rc::clone(&amp;head));

....
assert_eq!(1, tail.borrow().next.clone().unwrap().borrow().value);
</code></pre>
<p>What's the challenges of a circular linked list using Rc?</p>
<h3>🔵 Strong references may never reach zero</h3>
<p>Remember that the Rc underlying data is dropped and invalidated when the <code>Rc::strong_count</code> reaches zero.</p>
<p>But in a circular linked list, for instance, we may have a <strong>cyclic reference</strong>, which in turn will never make the <code>strong_count</code> to reach zero, <strong>leading to memory leaks</strong>.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8z6cj08ae1c2788j1uva.png" alt="cyclic references" /></p>
<p>In such a scenario, the <code>tail.next</code> is a "weak" reference. Rust provides a way for <code>Rc</code> to have a different counter, called <code>weak_count</code>.</p>
<p>Thus, the weak counter will not be used for deciding when Rust should drop the value from the heap.</p>
<p>For solving this problem, Rc brings a method called <code>downgrade</code>, which <strong>does not involve ownership at all</strong> and transforms a strong reference into a weak one.</p>
<p>This smart pointer is called <strong>Weak</strong> and it's a weak reference in an <em>Rc</em>.</p>
<p>Let's see a basic usage of downgrading or upgrading references in an Rc (see below in the comments):</p>
<pre><code class="language-rust">use std::rc::Rc;

// Just a strong reference
let name = Rc::new(String::from("John"));
assert_eq!(1, Rc::strong_count(&amp;name));

// Cloning Rc is a strong reference
let _other_name = Rc::clone(&amp;name);
assert_eq!(2, Rc::strong_count(&amp;name));
assert_eq!(0, Rc::weak_count(&amp;name));

// Downgrade makes it a weak reference
let weak_name = Rc::downgrade(&amp;name);
assert_eq!(2, Rc::strong_count(&amp;name));
assert_eq!(1, Rc::weak_count(&amp;name));

// Upgrade makes it a strong reference again
let upgraded_name = weak_name.upgrade().unwrap();
assert_eq!(3, Rc::strong_count(&amp;name));
assert_eq!(1, Rc::weak_count(&amp;name));
assert_eq!("John", *upgraded_name);
</code></pre>
<p>In a linked list, the <code>prev</code> should be the "weak" reference because starting from the head, the Rc has already strong references that make the entire linked list through the <code>next</code> pointers.</p>
<p>Now, let's explore the final solution of this entire blogpost, using <code>Rc</code> for <strong>shared ownership</strong>, <code>RefCell</code> for <strong>interior mutability</strong> and <code>Rc::Weak</code> for preventing cyclic references in a linked list:</p>
<pre><code class="language-rust">use std::rc::Rc;
use std::cell::RefCell;
use std::rc::Weak;

struct Node&lt;T&gt; {
    value: T,
    next: Option&lt;Rc&lt;RefCell&lt;Node&lt;T&gt;&gt;&gt;&gt;,
    prev: Option&lt;Weak&lt;RefCell&lt;Node&lt;T&gt;&gt;&gt;&gt;,
}

let tail = Rc::new(RefCell::new(Node { value: 2, prev: None, next: None }));
let head = Rc::new(RefCell::new(Node { value: 1, prev: None, next: Some(Rc::clone(&amp;tail)) }));

// Weak reference (no ownership)
tail.borrow_mut().prev = Some(Rc::downgrade(&amp;head));

// Strong reference (shared ownership)
tail.borrow_mut().next = Some(Rc::clone(&amp;head));

assert_eq!(1, head.borrow().value);
assert_eq!(2, head.borrow().next.clone().unwrap().borrow().value);
assert_eq!(1, tail.borrow().prev.clone().unwrap().upgrade().unwrap().borrow().value);
assert_eq!(1, tail.borrow().next.clone().unwrap().borrow().value);
</code></pre>
<hr />
<h2>Wrapping Up</h2>
<p>In this very post we demonstrated the fundamentals smart pointers in Rust and the problems they solve about memory management.</p>
<p>This post was written during a <a href="https://www.youtube.com/watch?v=bdZe0LjDUyk">live coding</a> while building a doubly linked list by explaining fundamental concepts of ownership, references, borrowing and smart pointers.</p>
<p>I hope you had fun while learning a bit more about the <em>Rust ownership mental model</em> as I did.</p>
<p><strong>Cheers!</strong></p>
<hr />
<h2>References</h2>
<p>https://doc.rust-lang.org/book/</p>
<p>https://en.wikipedia.org/wiki/Smart_pointer</p>
<p>https://ricardomartins.cc/2016/06/08/interior-mutability</p>
<p>https://www.youtube.com/watch?v=6VSgMbFNUuQ</p>
]]></description>
<pubDate>2023-11-01</pubDate>
</item>
<item>
<title>Compiladores, trampolim, deque e thread pool</title>
<link>https://leandronsp.com/articles/compiladores-trampolim-deque-e-thread-pool-dd1.html</link>
<guid>https://leandronsp.com/articles/compiladores-trampolim-deque-e-thread-pool-dd1.html</guid>
<description><![CDATA[<p>Faz alguns dias que não escrevo. Minha rotina mudou um pouco e também tenho focado em estudar algumas coisas que eu não tinha tanto domínio. Entretanto tenho algumas coisas pra compartilhar.</p>
<p>Vamos a isso.</p>
<hr />
<h2>Rinha de compiladores</h2>
<p>Em Setembro de 2023 aconteceu na tal #bolhaDev uma <a href="https://github.com/aripiprazole/rinha-de-compiler">outra competição</a> em formato de rinha, mas desta vez o desafio era a <strong>construção de um compilador/interpretador</strong> que fosse capaz de rodar um programa escrito em uma linguagem especificada para a rinha, chamada - guess what -, <em>rinha</em>.</p>
<p>A competição foi criada por duas meninas fantásticas e que entendem muito desse mundo de compiladores: a <a href="https://twitter.com/algebraic_sofia">Sofia</a> e a <a href="https://twitter.com/algebraic_gabi">Gabi</a>. Ambas tinham o intuito de despertar mais o interesse das pessoas nesse tema de compiladores, o que foi atingido com sucesso: <em>190 projetos</em> foram submetidos em diversas linguagens de programação. Teve até projetos <a href="https://github.com/jeffque/rinha-compiler">submetidos</a> em <a href="https://github.com/tiagosh/garbash">Bash</a> (desta vez não fui eu, risos).</p>
<p>Inicialmente eu só tinha interesse em acompanhar esta rinha de perto sem submeter, apenas pra aprender mais com essa galera que manja bastante. Eu já tinha feito algumas coisas bastante básicas sobre compiladores na faculdade há muito tempo atrás.</p>
<p>Mas após ver a live do brabo <a href="https://www.youtube.com/watch?v=FbCdhicY3sk">Navarro</a>, que trouxe uma didática excelente com sua versão em Rust, decidi entrar também com o intuito de aprender mais e praticar algumas técnicas.</p>
<p>Escolhi Ruby porque eu queria testar um negócio. E disto saiu o <a href="https://github.com/leandronsp/patropi">patropi</a>.</p>
<p>Okay, mas o que era pra ser feito de fato? Vamos voltar duas casas e entender primeiro <strong>o que raios é um compilador ou interpretador</strong>.</p>
<h3>👉🏽 Arquitetura de CPU e código de máquina</h3>
<p>Para que uma CPU possa processar informações, é necessário organizá-la em um conjunto coeso de instruções. A este conjunto organizado de instruções para uma determinada CPU chamamos de <strong>arquitetura da CPU</strong>.</p>
<p>Existem diversos tipos de arquiteturas de CPU disponíveis, e cada CPU traz um conjunto específico de instruções. Exemplos de arquiteturas: x86, x86-64 (64-bits), ARM (baseada em RISC), MIPS, SPARC, PowerPC dentre outros.</p>
<p>As instruções da CPU são mapeadas para componentes da arquitetura chamados <em>registradores</em>. Para manipularmos esses registradores, precisamos utilizar um conjunto de "códigos" que são pré-definidos pelo fabricante da arquitetura da CPU. Estes códigos são chamados de <strong>opcodes</strong>, ou código de máquina, ou instruções de máquina.</p>
<pre><code>0000000000401000 
  401000:       48 c7 c0 01 00 00 00    
  401007:       48 c7 c7 01 00 00 00    
  40100e:       48 c7 c6 00 20 40 00    
  401015:       48 c7 c2 0e 00 00 00    
  40101c:       0f 05                   
  40101e:       48 c7 c0 3c 00 00 00    
  401025:       48 31 ff                
  401028:       0f 05                   
</code></pre>
<p>Escrever programas em código de máquina pode ser bastante desafiador, por isso alguns sistemas operacionais e bibliotecas trazem consigo um programa que auxilia na "montagem" desse código de máquina, possibilitando assim escrever programas em uma linguagem mnemônica com base em letras, números e símbolos, sendo mais fácil de memorizar do que os opcodes.</p>
<p>A estes montadores chamamos de <strong>assemblers</strong>.</p>
<h3>👉🏽 Assemblers</h3>
<p>Com um assembler, podemos escrever código de montagem (assembly), que é convertido para código de máquina.</p>
<p>Exemplo de um simples programa escrito em assembly x86_64 que imprime <code>Hello, World!</code> no standard output:</p>
<pre><code>.section .data
hello:
    .ascii "Hello, World!\n"

.section .text
.global _start

_start:
    # write our string to stdout
    movq $1, %rax         # syscall number for sys_write
    movq $1, %rdi         # file descriptor 1 is stdout
    movq $hello, %rsi     # pointer to the hello string
    movq $14, %rdx        # length of the hello string plus newline character
    syscall               # invoke the kernel

    # exit
    movq $60, %rax        # syscall number for sys_exit
    xorq %rdi, %rdi       # exit code 0
    syscall               # invoke the kernel
</code></pre>
<p>Após executar o programa com o assembler <code>as</code> que vem acompanhado no GNU/Linux seguido do linker <code>ld</code>, temos o binário com o código de máquina.</p>
<pre><code class="language-bash">$ as -o hello.o hello.s 
$ ld -o hello hello.o
$ ./hello

Hello, World!
</code></pre>
<p>Uma forma de fazer o "disassembly" do binário e ver o assembly equivalente, é com o utilitário <code>objdump</code>, onde podemos ver as instruções de máquina (opcodes) mapeadas para cada instrução assembly contida no nosso código fonte:</p>
<pre><code class="language-bash">$ objdump -d hello

hello:     file format elf64-x86-64


Disassembly of section .text:

0000000000401000 &lt;_start&gt;:
  401000:       48 c7 c0 01 00 00 00    mov    $0x1,%rax
  401007:       48 c7 c7 01 00 00 00    mov    $0x1,%rdi
  40100e:       48 c7 c6 00 20 40 00    mov    $0x402000,%rsi
  401015:       48 c7 c2 0e 00 00 00    mov    $0xe,%rdx
  40101c:       0f 05                   syscall
  40101e:       48 c7 c0 3c 00 00 00    mov    $0x3c,%rax
  401025:       48 31 ff                xor    %rdi,%rdi
  401028:       0f 05                   syscall
</code></pre>
<p><em>Espetacular, não?</em></p>
<h3>👉🏽 Tá, mas e os compiladores?</h3>
<p>Os compiladores entram justamente na categoria de programas de mais alto nível que convertem para o assembly da arquitetura em questão ou diretamente para código de máquina.</p>
<p>Por exemplo, este simples programa escrito em C:</p>
<pre><code class="language-c">#include &lt;stdio.h&gt;

int main() {
    printf("Hello, World!\n");
    return 0;
}
</code></pre>
<p>Pode ser compilado para código de máquina utilizando o compilador <code>gcc</code>:</p>
<pre><code class="language-bash">$ gcc -o hello hello.c 
$ ./hello

Hello, World!
</code></pre>
<blockquote>
<p>É muito comum também compiladores converterem para uma representação intermediária (IR) antes de gerar o código de máquina, como é o caso do próprio gcc, LLVM, dentre outros.</p>
</blockquote>
<h3>👉🏽 E os interpretadores?</h3>
<p>A distinção e similaridade entre compiladores e interpretadores não é um senso bastante comum, embora algumas pessoas entendam que interpretador é um tipo de compilador.</p>
<p>Indo pelo sentido prático, podemos dizer que o compilador <em>compila</em> para instrução de máquina. Enquanto que o interpretador vai lendo linha por linha do código fonte e executando.</p>
<p>Interpretadores modernos possuem um processo interno de compilação em tempo real, chamado <strong>just-in-time</strong>, ou <em>JIT</em>, o que pode inclusive caracterizar interpretadores também no rol de compiladores.</p>
<p>Independente da definição correta ou não de compiladores/interpretadores, uma coisa que devemos ter em mente é que estão acontecendo diversas transformações em camadas até chegar ao código de máquina.</p>
<p>Nesse processo de transformação, acontecem análises e otimizações que vão afetar drasticamente a performance do programa.</p>
<h3>👉🏽 Frontend vs Backend</h3>
<p>Você pensava que a briga front vs back existia apenas na web, pois não? No mundo dos compiladores também!</p>
<blockquote>
<p>To brincando, gente. Não tem briga nenhuma não</p>
</blockquote>
<p><strong>Frontend</strong> é a etapa que faz a leitura do código fonte e gera uma árvore sintática. Esta árvore, como o próprio nome diz, segue uma estrutura de dados muito comum na computação, o que permite a análise de programas seguindo o conjunto de regras definido na gramática.</p>
<p>Basicamente, o frontend faz a análise léxica (gerando tokens válidos da especificação), e em seguida a análise sintática (parsing dos tokens), gerando a árvore sintática abstrata, ou <strong>AST</strong>.</p>
<p>Com a AST, o frontend pode ainda realizar análise semântica e até mesmo gerar um código intermediário (IR), se for o caso.</p>
<p>Já no <strong>Backend</strong> consiste na etapa de, a partir de uma AST ou IR, aplicar otimizações, análise estática (também chamada de <em>ahead-of-time compilation</em>, ou <strong>AOT</strong>), compilação em tempo de execução (JIT) e por fim a geração de assembly ou código de máquina.</p>
<p>Resumindo, então, alguns tipos de assemblers e compiladores:</p>
<ol>
<li>Assemblers</li>
</ol>
<ul>
<li>
<p>NASM</p>
</li>
<li>
<p>GNU as</p>
</li>
</ul>
<ol start="2">
<li>Frontend</li>
</ol>
<ul>
<li>
<p>Clang (para LLVM)</p>
</li>
<li>
<p>GCC</p>
</li>
<li>
<p>javac</p>
</li>
</ul>
<ol start="3">
<li>Backend</li>
</ol>
<ul>
<li>
<p>LLC/LLI (para LLVM)</p>
</li>
<li>
<p>GCC</p>
</li>
<li>
<p>JVM</p>
</li>
</ul>
<h3>👉🏽 Okay mas e a rinha</h3>
<p>Na rinha, foi disponibilizado um frontend para a especificação da linguagem, em formato de <a href="https://crates.io/crates/rinha">Rust crate</a>.</p>
<p>Bastando ter o Rust instalado, adicionamos a crate e, através do comando <code>rinha</code>, temos uma representação JSON da AST:</p>
<pre><code class="language-bash"># examples/hello.rinha
print("Hello, world")

# run frontend
$ rinha -p examples/hello.rinha

{
  "name": "examples/hello.rinha",
  "expression": {
    "kind": "Print",
    "value": {
      "kind": "Str",
      "value": "Hello, world",
      "location": {
        "start": 6,
        "end": 20,
        "filename": "examples/hello.rinha"
      }
...........
</code></pre>
<p>O desafio? Escrever um compilador/interpretador para este AST. <em>Simples assim.</em></p>
<hr />
<h2>Patropi e o trampolim</h2>
<p>Minha submissão escrita em Ruby foi batizada de <strong>Patropi</strong> (<em>idk either</em>). Optei por fazer um interpretador no formato <em>tree-walking interpreter</em>, que basicamente vai caminhando por cada nó da AST e executando.</p>
<p>Como não sou especialista em compiladores e otimizações, pra mim foi o caminho mais sensato naquele momento.</p>
<p>Um exemplo de código muito simples em Ruby, para interpretar o simples <code>print("Hello world")</code>:</p>
<pre><code class="language-ruby">def evaluate(term)
  case term
  in { kind: 'Str', value: value }; value.to_s
  in { kind: 'Print', value: next_term }; puts evaluate(next_term)
  else raise "Unknown term: #{term}"
  end
end
</code></pre>
<p>Note que o método <code>evaluate</code> é nosso ponto focal. Ele recebe um termo (nó da AST) e tenta buscar por um match específico. Caso dê match com uma <code>String</code> literal, retorna o valor. Mas se der match com um nó <code>Print</code>, chama o <code>evaluate</code> recursivamente.</p>
<h3>👉🏽 Recursão</h3>
<p><strong>Recursão</strong> é o caminho mais intuitivo para manipular uma árvore na computação. Quase todo compilador é feito inicialmente de forma recursiva na manipulação da árvore justamente porque é intuitivo.</p>
<blockquote>
<p>Enquanto há nó para percorrer, vou chamar minha função novamente, até chegar no fim do galho</p>
</blockquote>
<p>Não vou entrar muito nos detalhes dos trade-offs da recursão, mas se quiser se aprofundar nisto, sugiro a leitura do artigo que escrevi sobre <a href="https://leandronsp.com/articles/entendendo-fundamentos-de-recursao-2ap4">fundamentos de recursão</a>.</p>
<p>Com isto em mente, e sabendo que o pessoal na rinha iria executar alguns programas que exigem muito da memória stack, decidi experimentar uma estratégia não muito ortodoxa e que funciona como alternativa à recursão quando otimizações de recursão de cauda não são possíveis ou são muito limitadas.</p>
<p>A esta estratégia (que também explico com detalhes no artigo apontado), chamamos de <strong>Trampoline</strong>, ou <em>trampolim</em>.</p>
<h3>👉🏽 Trampolim para o resgate</h3>
<p>Basicamente, ao invés de chamar a função recursivamente, eu devolvo uma estrutura chamada "continuation", passando o controle para um loop fora da função.</p>
<p>Este loop imperativo toma a decisão de executar uma closure com o valor passado na continuation ou se vai para o próximo nó da árvore. A cada iteração do loop, por não haver chamada recursiva, não há acúmulo na stack frame, portanto diminui drasticamente a chance de acontecer <strong>stack buffer overflow</strong>, ou seja, praticamente não há chance de estouro de pilha.</p>
<p>Aqui segue uma imagem com a arquitetura do <a href="https://github.com/leandronsp/patropi">Patropi</a>:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jsbup3brs6qdax53p4sy.png" alt="arquitetura do patropi" /></p>
<blockquote>
<p>Para mais detalhes de código, basta seguir o link do <a href="https://github.com/leandronsp/patropi">repositório no Github</a></p>
</blockquote>
<h3>👉🏽 And the Oscar goes to...</h3>
<p>O <a href="https://github.com/aripiprazole/rinha-de-compiler/blob/main/TESTS.md">ranking oficial</a> está divulgado no repositório da rinha, e em primeiro lugar ficou uma solução escrita em Golang de tree-walking interpreter. Achei incrível, here we "Go" again :P</p>
<p>Minha solução em Ruby, o Patropi, das 190 submissões, ficou entre as 68 que rodaram, amargando a 65ª posição.</p>
<p>Apesar de ter sido divertido implementar o trampolim, Patropi não soube lidar muito bem com Tuplas e falhou em diversos testes...</p>
<hr />
<h2>Fila duplamente terminada em Go</h2>
<p>Neste intervalo de estudos, aproveitei também para contribuir para um projeto de <a href="https://github.com/kelvins/algorithms-and-data-structures">algoritmos e estruturas de dados</a> no Github. A ideia do repositório é muito boa, basicamente ali tem dezenas de algoritmos que podem ser implementados por qualquer pessoa em diversas linguagens. Basta contribuir :)</p>
<p>Aproveitei a deixa para submeter <a href="https://github.com/kelvins/algorithms-and-data-structures/pulls?q=is%3Apr+author%3Aleandronsp+is%3Aclosed">alguns algoritmos e estruturas de dados</a> em Rust, Go e Ruby. Mas queria destacar uma fila duplamente terminada (double-ended queue, ou <strong>deque</strong>) que <a href="https://github.com/kelvins/algorithms-and-data-structures/pull/211">fiz em Go</a>.</p>
<p><strong>Deque</strong> é uma estrutura de dados de fila que permite adicionar ou remover elementos em qualquer um dos lados.</p>
<p>Por exemplo, uma pilha permite adicionar e remover em apenas um lado (LIFO). Em uma fila, adicionamos a um lado e removemos do outro (FIFO).</p>
<p>A versatilidade da deque permite na média um acesso em tempo constante em ambos os lados (início da fila ou fim), tanto para adição (push) ou remoção (pop).</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cutdsqnv54nvvvehskxo.png" alt="deque" /></p>
<p>Um caso prático de deque seria no histórico de um navegador web, por exemplo. Como o armazenamento no navegador é limitado, o histórico precisa ter um limite de tamanho. Portanto adicionar e remover em ambas extremidades com o mesmo custo passa a ser uma vantagem neste cenário.</p>
<p>Em Go, poderíamos implementar em baby-steps da seguinte forma. Primeiro, definimos a type struct que terá um slice de inteiros:</p>
<pre><code class="language-go">package main

import "fmt"

type Deque struct {
	Store []int
}
</code></pre>
<p>Agora, temos que implementar as operações do lado direito, <code>Rpush</code> e <code>RPop</code>:</p>
<pre><code class="language-go">func (deque *Deque) RPush(element int) {
	deque.Store = append(deque.Store, element)
}

func (deque *Deque) RPop() *int {
	if len(deque.Store) == 0 {
		return nil
	}

	element := deque.Store[len(deque.Store)-1]
	deque.Store = deque.Store[:len(deque.Store)-1]

	return &amp;element
}
</code></pre>
<ul>
<li>
<p>RPush: utiliza o built-in <code>append</code> no lado direito, que é uma operação constante</p>
</li>
<li>
<p>RPop: manipula o slice, trabalhando apenas com os índices</p>
</li>
</ul>
<p>Para terminar, fazer o mesmo do lado esquerdo:</p>
<pre><code class="language-go">func (deque *Deque) LPush(element int) {
	deque.Store = append([]int{element}, deque.Store...)
}

func (deque *Deque) LPop() *int {
	if len(deque.Store) == 0 {
		return nil
	}

	element := deque.Store[0]
	deque.Store = deque.Store[1:]

	return &amp;element
}
</code></pre>
<p>No lado esquerdo, as operações passam a ter um custo linear, mas na média, por ser uma fila duplamente terminada, esse custo é amortizado caindo pra constante.</p>
<hr />
<h2>Thread pool no Adelnor</h2>
<p>Pra finalizar, meu projetinho xodó do momento, o <a href="https://github.com/leandronsp/adelnor">leandronsp/adelnor</a>, precisava de uns ajustes importantes. Cada request HTTP era servido dentro da thread principal, não havia qualquer tipo de concorrência, e portanto o server não conseguia entregar muitos requests.</p>
<p>Resolvi implementar uma thread pool modesta <a href="https://github.com/leandronsp/adelnor/pull/1">submetendo este PR</a>, em live na Twitch e também no <a href="https://www.youtube.com/watch?v=a8Ccxt0UGqA">Youtube</a>.</p>
<pre><code class="language-bash">$ gem install adelnor
</code></pre>
<pre><code class="language-ruby">require 'adelnor'

app = -&gt; (env) do
  [200, { 'Content-Type' =&gt; 'text/html' }, 'Hello world!']
end

Adelnor::Server.run app, 3000, thread_pool: 5
</code></pre>
<p>Foi um processo interessante. Após subir o server com uma pool de 5 threads, a app conseguiu entregar 4x mais requests do que na versão single-threaded.</p>
<p>E caso queira aprender mais sobre threads em Ruby, recomendo muito <a href="https://workingwithruby.com/wwrt/intro">este guia</a>.</p>
<hr />
<h2>Conclusão</h2>
<p>É isto que tive experimentando ultimamente. No momento, estou fazendo algumas melhorias no Adelnor, nomeadamente implementar modelo de atores com Ractors, mas vou entrar em detalhes disto num blogpost à parte.</p>
<hr />
<h2>Referências</h2>
<p><a href="https://computersciencewiki.org/index.php/Architecture_of_the_central_processing_unit_(CPU)">CPU architecture</a>
<a href="http://sparksandflames.com/files/x86InstructionChart.html">x86 instructions chart</a>
<a href="https://craftinginterpreters.com/">Crafting Interpreters</a>
<a href="https://github.com/aripiprazole/rinha-de-compiler">Rinha de compiladores</a>
<a href="https://github.com/reu/rinha-compiladores">reu/rinha-compiladores</a>
<a href="https://github.com/leandronsp/patropi">leandronsp/patropi</a>
<a href="https://leandronsp.com/articles/entendendo-fundamentos-de-recursao-2ap4">Fundamentos de Recursão</a>
<a href="https://en.wikipedia.org/wiki/Double-ended_queue">Double-ended queue</a>
<a href="https://workingwithruby.com/">workingwithruby.com</a>
<a href="https://github.com/leandronsp/adelnor">leandronsp/adelnor</a></p>
]]></description>
<pubDate>2023-10-17</pubDate>
</item>
<item>
<title>Rust, Go, Rinha e I/O</title>
<link>https://leandronsp.com/articles/rust-go-rinha-e-io-39o2.html</link>
<guid>https://leandronsp.com/articles/rust-go-rinha-e-io-39o2.html</guid>
<description><![CDATA[<p>Este artigo é o início de um formato diferente de conteúdo que quero experimentar, um apanhado (ou resumão, ou dump, como queiram chamar) de coisas que tenho visto nos últimos dias, com uma pegada informal e um leve toque de didática como de costume.</p>
<hr />
<h2>Rust, Go e background jobs</h2>
<p>Em 2022 eu estava aprendendo Rust.</p>
<p>Numa bela 4a feira de chuva, fiz um tweet dizendo que iria tentar implementar uma lista duplamente ligada em Rust. Minutos depois, fiz <a href="https://twitter.com/leandronsp/status/1578154810612027392?s=20">outro tweet</a> dizendo que iria desistir e ver Netflix.</p>
<p>Então desisti do Rust.</p>
<p>Recentemente em 2023 comecei uma saga para aprender Golang. Como a sintaxe me incomodou um pouco, talvez pela verbosidade dos <code>if err != nil</code> a dar com telha, decidi continuar kkk</p>
<p>Mas adicionando uma complexidade nisto tudo: voltar a aprender Rust junto com Go.</p>
<blockquote>
<p>As vezes eu meto dessas, em 2015/16 decidi aprender Elixir e redes neurais artificiais tudo junto, e saiu o <a href="https://github.com/leandronsp/morphine">morphine</a></p>
</blockquote>
<p>Nesta segunda tentativa com Rust, bateu um sentimento, confesso. <em>Sentimento bom</em>, no caso.</p>
<p>Com isto, passei a praticar em Rust e Go (e porque não Ruby) estruturas de dados simples que já estou habituado a implementar (pq pratico muito): filas, pilhas, listas ligadas, mutexes etc etc. E claro, programação em sockets como de praxe.</p>
<p>Desta massarocada toda saiu <a href="https://gist.github.com/leandronsp/31ad977cf1a68f5d7b3d13d46ab34a8c">um UNIX server</a> em Go, <a href="https://gist.github.com/leandronsp/b72b2b21d2ef8f8f3ccb2ab8af422c2b">outro</a> em Rust, pelo que indo de milho em milho, saiu um background job em <a href="https://gist.github.com/leandronsp/c0a7daaa27f75dd65d9fa803db8489ad">ambas</a> distintas <a href="https://gist.github.com/leandronsp/90f0e97ef28586c5c996835b311f9ac5">linguagens</a>.</p>
<p>Cheguei a um <a href="https://gist.github.com/leandronsp/90f0e97ef28586c5c996835b311f9ac5">background job em Rust</a> muito overkill, vale a pena dar uma olhada, lá abordo lista duplamente ligada, fila duplamente terminada, rpoplpush, blocking queue, DLQ, retries, threads etc.</p>
<p>Tudo isso implementado com os smart pointers em Rust, para resolver problemas inerentes (ok, não são bem problemas) a ownership e borrow checker.</p>
<blockquote>
<p>Em breve vou escrever sobre meu aprendizado em Rust com artigos mais detalhados, preciso arrumar um tempo na minha vida, salvar pinguins na Antártida é mais urgente que isso</p>
</blockquote>
<p>Mas se você acha que é  ̶m̶u̶i̶t̶o̶ ̶m̶a̶i̶s̶ ̶e̶l̶e̶g̶a̶n̶t̶e̶ mais fácil ver um código Ruby, <a href="https://github.com/leandronsp/fun/blob/master/dsa/queues/ruby/background_job.rb">aqui neste arquivo</a> no meu projeto <a href="https://github.com/leandronsp/fun">leandronsp/fun</a> também abordo um background job em Ruby com os mesmos conceitos aplicados ali em Rust e Go.</p>
<hr />
<h2>Rinha de backend</h2>
<p>Entre Julho e Agosto aconteceu na famosa #bolhaDev no Twitter uma competição chamada <a href="https://github.com/zanfranceschi/rinha-de-backend-2023-q3">rinha de backend</a>, criada pelo <a href="https://twitter.com/zanfranceschi">Zan do Twitter</a> e nosso arauto do sarcasmo <a href="https://twitter.com/wilcorrea">Will Correa</a>.</p>
<p>A ideia era que as pessoas participantes trouxessem a implementação de uma API definida nas regras da competição.</p>
<p>O desafio tinha como requisito uma arquitetura composta por basicamente <strong>um NGINX</strong> fazendo load balancing para <strong>2 API's</strong> mandando dados para <strong>um banco de dados</strong>, tudo rodando em containers.</p>
<blockquote>
<p>Se você quer saber mais sobre containers e Docker, dê uma olhadinha <a href="https://leandronsp.com/articles/kubernetes-101-part-i-the-fundamentals-23a1">nesta série de artigos</a> que escrevi centuries ago</p>
</blockquote>
<p>Pra deixar mais desafiador ainda, havia uma restrição obrigatória de recursos onde o total de containers não podia exceder o limite de 1.5 CPU's e 3GB de memória.</p>
<p>Em uma data previamente estipulada, todas as submissões seriam submetidas a test de stress (violento, diga-se de passagem) através de uma ferramenta chamada <a href="https://gatling.io/docs/gatling/tutorials/installation/">Gatling</a>.</p>
<p>Foi uma iniciativa muito bacana pois trouxe à luz pessoas que pouco interagiam por ali e que eram muito talentosas. Deu pra aprender e trocar muita figurinha durante os dias pré-submissão.</p>
<p>Outra coisa boa foi que o <a href="https://twitter.com/coproduto">polvo lá do Twitter</a> organizou um <a href="https://www.meetup.com/import-beer/events/295288014/">encontro presencial</a> que iria transmitir a live da rinha, onde deu pra conhecer mais pessoas interessadas no tema.</p>
<h3>Plain Ruby, Chespirito e Roda</h3>
<p>Decidi participar submetendo uma versão em Ruby (sem Rails) tentando colocar em prova um web framework muito simples que criei chamado <a href="https://github.com/leandronsp/chespirito">Chespirito</a>.</p>
<p>Infelizmente, durante os testes no meu ambiente local não consegui grandes números com o Chespirito e acabei indo de <a href="https://github.com/jeremyevans/roda">Roda</a>, que por acaso é o pior framework que já vi, pois sou muito hater de DSL's em situações onde não precisamos delas.</p>
<p>Um exemplo da tamanha verbosidade que atingimos com este framework:</p>
<pre><code class="language-ruby">r.get do              # GET
  r.on "a" do         # GET /a branch
    r.on "b" do       # GET /a/b branch
      r.is "c" do end # GET /a/b/c request
      r.is "d" do end # GET /a/b/d request
    end
  end
end

r.post do             # POST
  r.on "a" do         # POST /a branch
    r.on "b" do       # POST /a/b branch
      r.is "c" do end # POST /a/b/c request
      r.is "e" do end # POST /a/b/e request
    end
  end
end
</code></pre>
<blockquote>
<p>Escolhi Roda pq me disseram que era rápido. Não comparei com Sinatra, apenas confiei</p>
</blockquote>
<p>Submeti então minha versão naquela terça-feira sombria às 22h42 com Roda e <a href="https://github.com/puma/puma">Puma</a>, pois meu ambiente não me ajudou a tirar bons números com I/O assíncrono no <a href="https://github.com/socketry/falcon">Falcon</a>.</p>
<blockquote>
<p>Logo mais chegamos no ponto do meu ambiente</p>
</blockquote>
<p>Portanto, minha submissão ficou assim:</p>
<ul>
<li>
<p>Multi-threading com Puma, com uma modesta pool de threads 0:5, que é o default no Puma, e sem CPU workers</p>
</li>
<li>
<p><a href="https://github.com/mperham/connection_pool">Pool</a> de 5 conexões com o PostgreSQL</p>
</li>
<li>
<p>PostgreSQL nos defaults (100 max_connections)</p>
</li>
<li>
<p>NGINX nos defaults com um ligeiro aumento para 1024 worker_connections</p>
</li>
</ul>
<p>No docker-compose submetido, dividi os recursos da seguinte forma:</p>
<ul>
<li>
<p>0.4 CPU | 1GB mem para as API's (x2)</p>
</li>
<li>
<p>0.6 CPU | 0.8GB mem para o PostgreSQL</p>
</li>
<li>
<p>0.1 CPU | 0.2GB mem para o NGINX</p>
</li>
</ul>
<p>Depois vou explicar como que distribuindo melhor os recursos e <a href="https://twitter.com/leandronsp/status/1699568664859603184">diminuindo os números do NGINX e PostgreSQL</a> fez meu troughput melhorar, mas só consegui fazer isto dias depois da rinha ter terminado.</p>
<h3>O grande momento, the big moment</h3>
<p>Minha submissão Ruby ficou em 20º lugar num ranking de 51 submissões funcionais (quase 100 foram submetidas mas muitas não rodaram lá na máquina dos caras), com um total de 24k inserts no banco de dados após o teste de stress com carga dobrada.</p>
<p>Não achei um número ruim mas eu tinha uma noção de que o desafio era muito I/O-bound, coisa que expliquei <a href="https://twitter.com/leandronsp/status/1695470712738210189">nesta thread</a> do Twitter e também <a href="https://twitter.com/leandronsp/status/1699568664859603184">nesta outra thread</a> dias depois.</p>
<blockquote>
<p>Se I/O-bound ou CPU-bound são termos esquisitos pra você, sugiro dar um passo atrás e aprender os fundamentos de concorrência em sistemas operacionais, <a href="https://web101.leandronsp.com/">neste super guia</a> que escrevi anos atrás sobre o funcionamento da Web</p>
</blockquote>
<h3>Resumo da ópera</h3>
<p>O resumo disto é que depois com mais calma, consegui ajustar melhor meu ambiente de desenvolvimento. Abandonei o <a href="https://github.com/abiosoft/colima">colima</a> que tava com performance horrível no meu macOS e abracei o <a href="https://orbstack.dev/">orbstack</a>. So far, so good.</p>
<p>Com isto, pude rodar de forma mais assertiva com restrição de recursos os testes de stress no meu ambiente. Tem a ver com a virtualização do orbstack ser mais performática etc e tal.</p>
<h3>A new hope for plain Ruby</h3>
<p>Isto abriu portas para que eu voltasse a experimentar Falcon e meu filho Chespirito. Guess what, os números começaram a bater a famigerada dupla Roda/Puma.</p>
<p>Aproveitei também para utilizar o <a href="https://www.portainer.io/">Portainer</a> para visualizar métricas de CPU e memória dos containers no Docker.</p>
<p>Não apenas isto, também mexi nos meus limites no docker-compose, ficando assim, delegando mais recursos para o PostgreSQL que, tadinho, era o que mais apanhava (parabéns guerreiro, tmj):</p>
<ul>
<li>
<p>0.2 CPU | 0.3GB para API's (x2)</p>
</li>
<li>
<p>1 CPU | 1.7GB para PostgreSQL (sim, nessa arquitetura, db sempre gasta mais CPU e memória)</p>
</li>
<li>
<p>0.1 CPU | 0.1GB para o NGINX</p>
</li>
</ul>
<p>Diferente de muitas submissões na rinha, fiquei entre poucos que optaram por não utilizar qualquer estratégia de cache ou batch insert de forma assíncrona.</p>
<p>Meu intuito sempre foi experimentar algo que acredito muito e que trago nos projetos em que trabalho: não abusar de cache ou estratégia assíncrona onde não precisa. Cache ajuda mas pode trazer muitos desafios e encarecer custos no fim das contas.</p>
<blockquote>
<p>Claro que, para a rinha, tudo era válido. Foi um amontoado positivo de diferentes soluções e troca de conhecimento</p>
</blockquote>
<p>Mas como sou chato com custos, eu sempre vou pra solução mais simples possível e que causa menos entropia possível, <em>até que se prove o contrário</em>.</p>
<p>Cenário então fica assim:</p>
<ul>
<li>
<p>I/O não-bloqueante com Falcon, atendendo requests com multitasking cooperativo (Fibers)</p>
</li>
<li>
<p>Chespirito, que não faz grande coisa a não ser rotear mensagens do Rack para a lógica devida, mas com código muito mais explícito e sem aquela DSL  ̶h̶o̶r̶r̶o̶r̶o̶s̶a̶ estranha do Roda</p>
</li>
</ul>
<p>Os números melhoraram, indo pra 35k. Not bad. Mas eu tinha uma leve suspeita de que algo errado ainda estava com minha solução. Por ser um desafio muito I/O-heavy, os requests ficavam pouco no Ruby, então eu tinha que melhorar a latência do PostgreSQL.</p>
<p>Foi aí que inverti a lógica.</p>
<h3>Fechando a torneira</h3>
<p><a href="https://twitter.com/leandronsp/status/1699568664859603184">Nesta thread</a> compartilhei recentemente como consegui atingir 46k inserts, mas em suma eu basicamente percebi que muitos requests à espera (Gatling judia) fazem aumentar a latência e consequentemente ciclo de CPU para fazer gestão das filas nos sockets.</p>
<p>Minha ideia então foi não deixar muito requests à espera, mesmo porque as queries no db são muito rápidas (remember kids, índices corretos salvam vidas).</p>
<p>Para atingir isto, resolvi diminuir o PostgreSQL para 30 max_connections e NGINX para 256 worker_connections (podia até ser 128 ou 64 tbh). Na API, como são duas, deixei uma pool de 15 conexões, pois o PostgreSQL neste caso iria até 30.</p>
<p>O resultado trouxe um troughput melhor e garantiu 46k inserts.</p>
<blockquote>
<p>Em breve vou escrever um artigo mais detalhado sobre esta saga do Ruby na rinha</p>
</blockquote>
<h3>E o Bash?</h3>
<p>It turns out que também fiz <a href="https://github.com/leandronsp/rinha-backend-bash">outra versão</a> e submeti, escrita em Bash script, apenas for fun mesmo. Foi produto de um tweet inocente que fiz, o pessoal não perdoou e fez o tweet viralizar, pelo que me senti obrigado e implementar a API em Bash.</p>
<p>Na verdade, como eu já venho de uma saga ensinando fundamentos de computação <a href="https://leandronsp.com/articles/building-a-web-server-in-bash-part-i-sockets-2n8b">nos meus artigos</a> usando Bash, foi tranquilo fazer uma versão minimamente aceitável com mkfifo e netcat.</p>
<p>Submeti sem grandes pretensões, rodei o teste local apenas uma vez e deu um monte de erro, pensei "freak it vou mandar mesmo assim" e foi.</p>
<p>Para meu deleite, a solução em Bash ficou dentre as 51 funcionais da rinha, conquistando a tão sonhada 51ª posição, com um total de 17 inserts.</p>
<blockquote>
<p>Isso mesmo, 17 inserts</p>
</blockquote>
<p>Twitter não perdoa e então começaram a me chamar de "carinha do Bash", "ministro dos scripts Bash" e etc, mas gente <strong>EU NAO PROGRAMO EM BASH</strong>. Sou apenas um dev scriptzero que usa Bash as vezes pra facilitar minha vida e automatizar o que não preciso, mas longe de "manjar" de Bash kkkkk</p>
<hr />
<h2>I/O assíncrono e live coding do Leandro</h2>
<p>Com o término da rinha, foi gerado um buzz muito alto em torno de I/O, principalmente o famoso <strong>I/O assíncrono</strong>, ou então como alguns costumam referenciar por <strong>I/O não-bloqueante</strong>.</p>
<p>Em breve escrevo artigos mais formais e detalhados sobre I/O não-bloqueante, mas recentemente fui de <a href="https://www.youtube.com/watch?v=w1ejKYlxhaA">live mesmo</a>, com um formato mais de "explicação", indo lá atrás de forma resumida nos aspectos de concorrência em sistemas operacionais até chegar em I/O não-bloqueante.</p>
<p>Na <a href="https://www.youtube.com/watch?v=w1ejKYlxhaA">live</a> eu trouxe exemplos em Ruby, Bash e C. Se você quer dar uma espreitada no que aconteceu por lá, <a href="https://www.youtube.com/watch?v=w1ejKYlxhaA">CLIQUE AQUI</a>.</p>
<hr />
<h2>That's all folks</h2>
<p>É isto, o intuito deste breve artigo foi fazer um apanhado das coisas que tenho olhado recentemente, em um formato mais informal como costumo usar no Twitter.</p>
<p>Espero que o formato possa ser útil, caso contrário irei apenas continuar com aquele formato  ̶c̶h̶a̶t̶o̶ denso e didático.</p>
<p>Claro que não deixarei de escrever os artigos técnicos de costume. E pode ser que eu misture inglês com português e a massarocada toda.</p>
<p>Fiquem ligades.</p>
]]></description>
<pubDate>2023-09-08</pubDate>
</item>
<item>
<title>Entendendo fundamentos de recursão</title>
<link>https://leandronsp.com/articles/entendendo-fundamentos-de-recursao-2ap4.html</link>
<guid>https://leandronsp.com/articles/entendendo-fundamentos-de-recursao-2ap4.html</guid>
<description><![CDATA[<p>Se pra você:</p>
<ul>
<li>
<p><strong>Recursão</strong> é um tema obscuro ou quer entender mais um pouco sobre;</p>
</li>
<li>
<p><strong>Tail call e TCO</strong> são meios de comunicação alienígena e;</p>
</li>
<li>
<p><strong>Trampoline</strong> é nome de remédio</p>
</li>
</ul>
<p><em>Então este artigo é pra você.</em></p>
<p>Aqui, vou explicar o que são estes termos de forma didática e os problemas que resolvem, com exemplos em <strong>Ruby</strong>. Mas não se preocupe pois os exemplos são bem simples de entender, mesmo porque os conceitos aqui mostrados são <em>agnósticos a linguagem</em>.</p>
<p>Portanto, venha comigo nesta viagem <strong>interminável</strong>.</p>
<blockquote>
<p>✋
Para continuar, volte ao topo do post</p>
</blockquote>
<hr />
<h2>Agenda</h2>
<ul>
<li>
<p><a href="#o-que-%C3%A9-recurs%C3%A3o">O que é recursão</a></p>
</li>
<li>
<p><a href="#fibo-para-os-%C3%ADntimos">Fibo para os íntimos</a></p>
</li>
<li>
<p><a href="#tail-call">Tail call</a></p>
</li>
<li>
<p><a href="#stack-e-stack-overflow">Stack e stack overflow</a></p>
</li>
<li>
<p><a href="#tail-call-optimization">Tail call optimization</a></p>
</li>
<li>
<p><a href="#trampoline">Trampoline</a></p>
</li>
<li>
<p><a href="#conclus%C3%A3o">Conclusão</a></p>
</li>
<li>
<p><a href="#refer%C3%AAncias">Referências</a></p>
</li>
</ul>
<hr />
<h2>O que é recursão</h2>
<p>Em programas de computador, somos habituados a <strong>quebrar problemas grandes em problemas menores</strong> por meio do uso de funções ou métodos.</p>
<p><strong>Recursão</strong> é, de forma extremamente simplificada, uma técnica na computação onde estes problemas são quebrados de forma que uma <em>determinada função é executada recursivamente</em>.</p>
<p>Com isto, a função "chama a si mesma" para resolver alguma computação e continuar sua execução.</p>
<hr />
<h2>Fibo para os íntimos</h2>
<p>Um exemplo bastante clássico de recursão é descobrir, dada a <strong>sequência de Fibonacci</strong>, ou Fibo, qual número se encontra em determinada posição.</p>
<pre><code>0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55.........
</code></pre>
<p>Com isto, a função <strong>fib</strong> traria resultados como:</p>
<pre><code>fib(0) = 0
fib(1) = 1
fib(2) = 1
...
fib(7) = 13
fib(10) = 55
</code></pre>
<p>Temos então uma possível implementação recursiva em Ruby:</p>
<pre><code class="language-ruby">def fib(position)
  return position if position &lt; 2

  fib(position - 1) + fib(position - 2)
end
</code></pre>
<p>Este código, entretanto, não é performático. Ao tentar buscar o número da posição 10000 (dez mil) na sequência, o programa fica muito lento pois faz inúmeras chamadas <strong>recursivas redundantes</strong>.</p>
<pre><code>                 fib(10)
             /                \
     fib(9)                 fib(8)
        /          \          /   \
fib(8)     fib(7)     fib(7)    fib(6)
  /      \       /       \       /   \
fib(7) fib(6) fib(6) fib(5) fib(6) fib(5)
   /    \     /     \     /     \     /    \
fib(6) fib(5) fib(5) fib(4) fib(5) fib(4) fib(5) fib(4)
  /   \   /   \   /   \   /   \   /   \   /   \   /   \
...
</code></pre>
<p>Consequentemente, quanto maior o input da função, o tempo de execução deste código tende a crescer de forma exponencial, que em notação <strong>Big-O</strong> seria <code>O(2^n)</code>.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n9udpxmgl34093mpmqvc.png" alt="big O exponencial" /></p>
<p>É possível reduzir esta complexidade?</p>
<p>E se tentarmos aplicar uma técnica onde a última chamada da função, ao invés de ser a soma de duas chamadas recursivas, passa a ser apenas <strong>uma chamada recursiva</strong>, sem realizar computações adicionais?</p>
<p>Esta técnica existe e é chamada de <strong>tail call</strong>, ou <em>tail recursion</em>.</p>
<hr />
<h2>Tail call</h2>
<p><strong>Tail call</strong>, ou <strong>TC</strong>, consiste em uma função recursiva onde a última chamada recursiva é a própria função sem computação adicional.</p>
<p>Com isto, reduzimos a complexidade de exponencial para linear, como se fosse um simples loop iterando em uma lista de inputs.</p>
<p>Em notação Big-O isto fica <code>O(n)</code>, ou seja, a complexidade cresce de forma linear acompanhando o crescimento do input.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z8uj2fzw9090971awu89.png" alt="big O linear" /></p>
<p>Exemplo em Ruby:</p>
<pre><code class="language-ruby">def fib(position, _current = 0, _next = 1)
  return _current if position &lt; 1

  fib(position - 1, _next, _current + _next)
end
</code></pre>
<p>Portanto, o número de chamadas recursivas é reduzido drasticamente para algo do tipo:</p>
<pre><code>fib(10, 0, 1)
fib(9, 1, 1)
fib(8, 1, 2)
fib(7, 2, 3)
fib(6, 3, 5)
fib(5, 5, 8)
fib(4, 8, 13)
fib(3, 13, 21)
fib(2, 21, 34)
fib(1, 34, 55)
fib(0, 55, 89)
</code></pre>
<p>Repara como que o número de chamadas recursivas diminuiu, ou seja, o código está seguindo um caminho mais <strong>linear</strong> com esta abordagem.</p>
<p>Assim, ao rodar o programa <strong>fib com TC</strong>, o tempo de execução é exponencialmente menor do que rodar sem TC, ficando <em>dezenas de milhares de vezes mais rápido</em>.</p>
<blockquote>
<p>✋
Claramente um programa que leva tempo exponencial é péssimo em termos de performance, não?</p>
</blockquote>
<pre><code class="language-ruby"># Sem TC
fib(30) # 0.75 segundos

# Com TC
fib(30) # 0.000075 segundos
</code></pre>
<p>Voltando ao exemplo de <code>fib(10000)</code>, ao rodar com TC, vemos que a execução é muito mais rápida, porém:</p>
<pre><code>recursion/fib.rb:10:in `fib_tc': stack level too deep (SystemStackError)
        from recursion/fib.rb:10:in `fib_tc'
        from recursion/fib.rb:10:in `fib_tc'
        from recursion/fib.rb:10:in `fib_tc'
        from recursion/fib.rb:10:in `fib_tc'
        from recursion/fib.rb:10:in `fib_tc'
        from recursion/fib.rb:10:in `fib_tc'
        from recursion/fib.rb:10:in `fib_tc'
        from recursion/fib.rb:10:in `fib_tc'
</code></pre>
<p><em>Uh oh</em>, um <strong>stack overflow!</strong></p>
<p>Para entender o que está acontecendo, vamos primeiro entender o que raios é uma <strong>stack</strong> e <strong>stack overflow</strong>.</p>
<hr />
<h2>Stack e stack overflow</h2>
<p>Quando um programa é executado, é alocada na memória uma estrutura de dados em formato de <em>pilha</em>, chamada <strong>Stack</strong>, que é utilizada para guardar os dados que estão sendo utilizados em uma função em execução.</p>
<blockquote>
<p>✋
Há também outra estrutura na memória do programa chamada <strong>Heap</strong>, que não é uma pilha e tem outras características que vão além do escopo deste artigo. Para entender recursão, focamos apenas na stack</p>
</blockquote>
<p>Quando o programa entra em uma função ou método, cada dado é <em>inserido (push) na stack</em>. Ao terminar a função, é feita a <em>remoção (pop) de cada dado</em>.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rstqf6yz2yph2d7byeuq.png" alt="stack" /></p>
<p>A cada chamada de função, é atribuído um novo <em>stack frame</em>. Como uma chamada recursiva nunca termina, o runtime não sabe que é preciso fazer "pop" dos dados e finalizar o frame, então a cada chamada, uma nova stack frame é criada e <strong>mais elementos são adicionados</strong> à stack.</p>
<p>Adivinha o que acontece quando adicionamos muitos dados na stack a ponto de <strong>ultrapassar seu limite</strong> na memória do computador?</p>
<p>Sim, acontece o famigerado <strong>Stack overflow</strong> 💥🪲, e isto explica aquele erro no Ruby ao rodar fib de 10000 com tail call.</p>
<blockquote>
<p>✋
Então quer dizer que calcular o fib de 10000 é um problema impossível de resolver com recursividade?</p>
</blockquote>
<p>Calma, algumas linguagens empregam uma técnica de otimização que consiste em utilizar a chamada TC com <em>apenas um stack frame</em>, garantindo assim que cada chamada recursiva seja tratada como se fosse <strong>uma iteração num loop primitivo.</strong></p>
<p>Com isto, é feita a manipulação dos argumentos e dados da função em uma única stack frame, exatamente como se tivéssemos escrito um loop primitivo. E consequentemente, novas chamadas recursivas de cauda não vão provocar estouro na pilha.</p>
<p>A esta técnica chamamos de <strong>Tail call optimization</strong>, ou <em>TCO</em>.</p>
<hr />
<h2>Tail call optimization</h2>
<p>Devido a sua natureza imperativa, e assim como diversas outras linguagens de propósito geral, <em>Ruby não traz suporte nativo a TCO</em>.</p>
<p>Geralmente esta funcionalidade é mais encontrada em linguagens com forte inclinação ao paradigma funcional, e não ao imperativo.</p>
<p>Mas em Ruby é possível <em>habilitar o modo TCO</em> com uma simples configuração na instrução do runtime do Ruby (YARV), e assim conseguimos executar fib de 10000 sem dor.</p>
<pre><code class="language-ruby">RubyVM::InstructionSequence.compile_option = {
  tailcall_optimization: true,
  trace_instruction: false
}

def fib(position, _current = 0, _next = 1)
  return _current if position &lt; 1

  fib(position - 1, _next, _current + _next)
end

# TC com TCO
fib(10000) # 0.02 segundos
</code></pre>
<p><strong>Superb</strong>! Com TCO habilitado, uma fib 10000 com tail call é executada em <em>0.02 segundos</em>!</p>
<p><em>Vale lembrar que TCO é uma técnica utilizada não apenas em recursão mas também em otimização de geração de instruções em compiladores,
mas isto foge ao escopo deste artigo.</em></p>
<blockquote>
<p>✋
Okay, mas e quando não for possível habilitar TCO para recursão de cauda ou eu estiver programando em uma linguagem que não tenha suporte a TCO?</p>
</blockquote>
<p><strong>Trampoline</strong> para o resgate.</p>
<hr />
<h2>Trampoline</h2>
<p>Para entendermos <em>trampoline</em>, vamos pensar no problema e em uma possível solução.</p>
<p>Se pensarmos com inteligência, podemos inicialmente concluir que a recursão deve ser evitada, e esta é a <em>premissa número um</em>.</p>
<pre><code class="language-ruby">def fib(position, _current = 0, _next = 1)
  return _current if position &lt; 1

  ###################################
  #### Devemos evitar isso!!!!!! ####
  ###################################
  fib(position - 1, _next, _current + _next)
end
</code></pre>
<p>Premissa dois, ao invés de retornar uma chamada recursiva diretamente, e se a retornarmos <strong>encapsulada em uma estrutura de função anônima que guarda contexto</strong> para ser executada em outro contexto?</p>
<blockquote>
<p>Sim, tipo uma closure ou lambda para os mais atentos</p>
</blockquote>
<p>Em Ruby, podemos utilizar o conceito de <strong>lambdas</strong>.</p>
<pre><code class="language-ruby">def fib(position, _current = 0, _next = 1)
  return _current if position &lt; 1
  
  lambda do
    fib(position - 1, _next, _current + _next)
  end
end
</code></pre>
<p>Se chamarmos <code>result = fib(0)</code>, por causa da primeira linha de short-circuit (<code>position &lt; 1</code>), o retorno do método é <code>0</code>.</p>
<p>Mas se chamarmos <code>result = fib(10)</code>, o retorno não será uma chamada recursiva, mas sim o <strong>retorno será uma função anônima</strong> (lambda).</p>
<p>Com isto, o método é então finalizado e a <em>stack é limpa</em>, ou seja, é feito o <strong>pop dos dados</strong> de dentro do método.</p>
<p>Como lambdas guardam contexto, se chamarmos <code>result.call</code>, a lambda é executada com o contexto anterior, que pode retornar o número final (caso entre no short-circuit) ou outra lambda com o novo contexto.</p>
<p>E assim, <strong>ficamos em loop até termos o valor final</strong>, enquanto o retorno atual continuar sendo uma lambda. Conseguiu entender o que podemos fazer?</p>
<p>Sim, um <em>loop!</em></p>
<pre><code class="language-ruby">result = fib(10000)

while result.is_a?(Proc)
  result = result.call
end

puts result
</code></pre>
<p>Output (um número mesmo muito grande):</p>
<pre><code>33644764876431783266621612005107543310302148460680063906564769974680081442166662368155595513633734025582065332680836159373734790483865268263040892463056431887354544369559827491606602099884183933864652731300088830269235673613135117579297437854413752130520504347701602264758318906527890855154366159582987279682987510631200575428783453215515103870818298969791613127856265033195487140214287532698187962046936097879900350962302291026368131493195275630227837628441540360584402572114334961180023091208287046088923962328835461505776583271252546093591128203925285393434620904245248929403901706233888991085841065183173360437470737908552631764325733993712871937587746897479926305837065742830161637408969178426378624212835258112820516370298089332099905707920064367426202389783111470054074998459250360633560933883831923386783056136435351892133279732908133732642652633989763922723407882928177953580570993691049175470808931841056146322338217465637321248226383092103297701648054726243842374862411453093812206564914032751086643394517512161526545361333111314042436854805106765843493523836959653428071768775328348234345557366719731392746273629108210679280784718035329131176778924659089938635459327894523777674406192240337638674004021330343297496902028328145933418826817683893072003634795623117103101291953169794607632737589253530772552375943788434504067715555779056450443016640119462580972216729758615026968443146952034614932291105970676243268515992834709891284706740862008587135016260312071903172086094081298321581077282076353186624611278245537208532365305775956430072517744315051539600905168603220349163222640885248852433158051534849622434848299380905070483482449327453732624567755879089187190803662058009594743150052402532709746995318770724376825907419939632265984147498193609285223945039707165443156421328157688908058783183404917434556270520223564846495196112460268313970975069382648706613264507665074611512677522748621598642530711298441182622661057163515069260029861704945425047491378115154139941550671256271197133252763631939606902895650288268608362241082050562430701794976171121233066073310059947366875
</code></pre>
<p>🔑 <strong>Ponto-chave</strong>
E com isto, amigues, temos a técnica <strong>trampoline</strong>: um <strong>loop</strong> primitivo não-recursivo que fica chamando outra função <em>escrita de forma recursiva</em> mas que retorna uma lambda com contexto, <strong>até chegar ao valor final</strong>.</p>
<p>Este código, <strong>sem TCO</strong>, para o <strong>fib de 10000</strong>, leva 0.04 segundos, um resultado muito próximo a TCO e sem causar stack overflow.</p>
<p><em>Incrível, não?</em> Agora não há desculpas para não escrever uma função de modo recursivo em linguagens que não trazem suporte a TCO 😛</p>
<hr />
<h2>Conclusão</h2>
<p>Neste artigo, o intuito foi trazer alguns conceitos que tocam no tema <strong>recursão</strong>. Estes conceitos fazem overlap com temas muito acadêmicos que, por vezes, dificultam o entendimento de pessoas que estão iniciando na área ou que não têm um background muito acadêmico.</p>
<p>Espero ter esclarecido de forma didática o assunto recursão, se puder deixe nos comentários qualquer correção ou informação relevante.</p>
<hr />
<h2>Referências</h2>
<p>https://twitter.com/leandronsp/status/1672043065001869312
https://twitter.com/JeffQuesado/status/1671954585987022882
https://en.wikipedia.org/wiki/Fibonacci_sequence
https://en.wikipedia.org/wiki/Recursion
https://www.geeksforgeeks.org/stack-data-structure/
https://en.wikipedia.org/wiki/Tail_call
https://en.wikipedia.org/wiki/Trampoline_(computing)
https://nithinbekal.com/posts/ruby-tco/
https://www.bigocheatsheet.com/
https://ruby-doc.org/core-3.1.0/RubyVM/InstructionSequence.html#method-c-compile_option</p>
]]></description>
<pubDate>2023-06-23</pubDate>
</item>
<item>
<title>Vencendo os números de ponto flutuante: um guia de sobrevivência</title>
<link>https://leandronsp.com/articles/vencendo-os-numeros-de-ponto-flutuante-um-guia-de-sobrevivencia-4n7n.html</link>
<guid>https://leandronsp.com/articles/vencendo-os-numeros-de-ponto-flutuante-um-guia-de-sobrevivencia-4n7n.html</guid>
<description><![CDATA[<h2>TL;DR</h2>
<p>Se quer poupar tempo e ir direto ao assunto, para cálculos precisos, prefira decimais de precisão arbitrária ou equivalentes como BigDecimal em vez de números de ponto flutuante.</p>
<p>Além disso, evite arredondamentos desnecessários. Quando necessário, limite o arredondamento apenas na etapa final para manter o máximo de precisão possível.</p>
<blockquote>
<p>Se você tá sem tempo, pode parar por aqui pois estas dicas já são suficientes para a maioria das pessoas</p>
</blockquote>
<p><em>Mas se você tem curiosidade em entender mais sobre este assunto, sugiro continuar nesta viagem aos números de ponto flutuante</em>.</p>
<hr />
<h2>Sumário</h2>
<ul>
<li>
<p><a href="#pr%C3%B3logo">Prólogo</a></p>
</li>
<li>
<p><a href="#first-things-first">First things first</a></p>
</li>
<li>
<p><a href="#bits-n%C3%A3o-s%C3%A3o-suficientes">Bits não são suficientes</a></p>
</li>
<li>
<p><a href="#bits-e-inteiros">Bits e inteiros</a></p>
</li>
<li>
<p><a href="#bits-e-outros-n%C3%BAmeros-reais">Bits e outros números reais</a></p>
</li>
<li>
<p><a href="#representa%C3%A7%C3%A3o-de-ponto-fixo">Representação de ponto fixo</a></p>
</li>
<li>
<p><a href="#representa%C3%A7%C3%A3o-de-ponto-flutuante">Representação de ponto flutuante</a></p>
</li>
<li>
<p><a href="#problemas-e-padr%C3%B5es">Problemas e padrões</a></p>
</li>
<li>
<p><a href="#tipos-de-dados-de-ponto-flutuante">Tipos de dados de ponto flutuante</a></p>
</li>
<li>
<p><a href="#problemas-de-ponto-flutuante-na-pr%C3%A1tica">Problemas de ponto flutuante na prática</a></p>
</li>
<li>
<p><a href="#decimais-ao-resgate">Decimais ao resgate</a></p>
</li>
<li>
<p><a href="#cuidado-com-o-arredondamento">Cuidado com o arredondamento</a></p>
</li>
<li>
<p><a href="#decimais-em-outras-tecnologias">Decimais em outras tecnologias</a></p>
</li>
<li>
<p><a href="#conclus%C3%A3o">Conclusão</a></p>
</li>
<li>
<p><a href="#refer%C3%AAncias">Referências</a></p>
</li>
</ul>
<hr />
<h2>📜 Prólogo</h2>
<p>Ah, sim, números de <strong>ponto flutuante</strong>.</p>
<p>Essas coisinhas que frequentemente aparecem em conteúdos técnicos, cheios de notações científicas e explicações complexas.</p>
<p>É quase certo que toda pessoa que está envolvida com software já tenha se deparado com a noção de que trabalhar com números de ponto flutuante pode ser <strong>perigoso</strong>, resultando em resultados de aritmética <strong>imprecisos</strong>, entre outros problemas.</p>
<p>No entanto, compreender todas as razões subjacentes por trás desse <em>tópico crucial em ciência da computação</em> pode ser desafiador para muitos.</p>
<p>No post de hoje, iremos aprofundar nos <strong>problemas que os números de ponto flutuante abordam</strong> e explorar as <strong>armadilhas envolvidas</strong>.</p>
<p>Então, pegue uma garrafa de água refrescante e embarque nesta jornada rumo à essência dos números de <em>ponto flutuante</em>.</p>
<hr />
<h2>👍🏼 First things first</h2>
<p>Computadores só entendem <strong>linguagem de máquina</strong>.</p>
<p><em>Linguagem de máquina</em> é uma coleção de "bits" que contém dados e instruções para a CPU. Representamos esses bits como <strong>bits binários</strong> e, como tal, é chamado de <strong>sistema numérico binário</strong> (0 e 1).</p>
<pre><code class="language-bits">01001001 01001000 11001011 01000001 01001000 10001000
01011001 01001000 01000001 01101001 01001000 01001001
11000001 10001000 01001001 11001010 10001000 01001000
11001001 01001000 11001001 01001000 01001000 01001001
</code></pre>
<p>Programar diretamente em linguagem de máquina é altamente propenso a erros e muitas vezes ineficiente em diversos cenários. Para lidar com isso, as <strong>linguagens assembly</strong> foram introduzidas ao longo dos anos, servindo como uma ponte entre as especificidades da arquitetura da CPU e um conjunto de instruções de alto nível.</p>
<p>Uma <strong>linguagem assembly</strong> (ou simplesmente <strong>Assembly</strong>) é traduzida em código de máquina por meio de um programa dedicado chamado <strong>Assembler</strong>. Cada arquitetura de CPU geralmente tem seu próprio assembler associado a ela.</p>
<p>Isso permite que programadores trabalhem com um conjunto de instruções mais gerenciável e legível para humanos, que é então traduzido em código de máquina específico para a arquitetura do processador.</p>
<pre><code class="language-assembly">section .data
    number1 dd 10      ; Define o primeiro número como um float de 32 bits
    number2 dd 20      ; Define o segundo número como um float de 32 bits

section .text
    global _start
_start:
    ; Carrega o primeiro número no registro xmm0
    movss xmm0, dword [number1]
    
    ; Carrega o segundo número no registro xmm1
    movss xmm1, dword [number2]
.....
.....
</code></pre>
<p>Os avanços no campo da engenharia de computação abriram caminho para o desenvolvimento de linguagens de programação cada vez mais de alto nível que podem ser traduzidas diretamente em instruções de código de máquina.</p>
<p>Ao longo das décadas seguintes, surgiram linguagens como <strong>C, Java e Python</strong>, entre outras, permitindo que cada vez mais pessoas pudessem escrever programas para computador sem necessariamente saber os detalhes de sua arquitetura de CPU.</p>
<p>Essa conquista significativa teve um impacto profundo na indústria, à medida que os computadores se tornaram mais compactos e rápidos, capacitando práticas modernas de engenharia de software para oferecer um valor substancial aos negócios em todo o mundo.</p>
<p>Computadores entendem <strong>bits</strong>, mas seres humanos se comunicam muito além de bits.</p>
<hr />
<h2>🔵 Bits não são suficientes</h2>
<p>Como mencionado anteriormente, os computadores entendem apenas <strong>bits binários</strong>.</p>
<p>Nada mais neste mundo pode ser interpretado por computadores.</p>
<p>Bits. Nada mais.</p>
<blockquote>
<p>💡 Na verdade, CPUs de computadores eletrônicos entendem apenas a ausência ou presença de tensão, permitindo-nos representar informações usando 0 e 1 (desligado e ligado).</p>
</blockquote>
<p>No entanto, a vida real traz desafios em que programas de computador, criados <strong>por pessoas para pessoas</strong>, precisam representar um conjunto mais amplo de caracteres além de apenas 0s e 1s. Isso inclui letras, números decimais, números hexadecimais, caracteres especiais, sinais de pontuação e até mesmo emojis como este 😹.</p>
<p>Conjuntos de caracteres padrão, como os esquemas <strong>ASCII</strong> e <strong>Unicode</strong>, resolvem o desafio de representar números, letras, caracteres especiais, emojis e muito mais dentro do sistema binário.</p>
<blockquote>
<p>⚠️ Explorar as complexidades da codificação de caracteres com ASCII e Unicode está além do escopo deste artigo. Isto será abordado em futuros posts</p>
</blockquote>
<p><em>Aqui, nosso foco será especificamente como os computadores trabalham com números na memória</em>, particularmente números <strong>inteiros</strong>.</p>
<hr />
<h2>🔵 Bits e inteiros</h2>
<p>Vamos utilizar o número <em>65</em> como exemplo. Ele é representado no sistema de numeração <strong>decimal</strong> (base 10), tornando-o um <em>número real</em>.</p>
<p>Além disso, ele é classificado como um <strong>número inteiro</strong>.</p>
<p>Ao realizar conversões com base em potências de 2, podemos representar o inteiro 65 como <code>01000001</code> em formato binário de 8 bits. Essa representação binária pode ser convertida de volta e para o valor decimal 65.</p>
<p>De uma <em>perspectiva matemática</em>, como <strong>65 é um número inteiro</strong>, ele cabe em um único byte (8 bits). Além disso, realizando potências de 2, sabemos que um único byte pode acomodar 256 números:</p>
<pre><code>2^8 = 256
</code></pre>
<p>Falando de forma simplificada, alguém pode assumir que um único byte pode representar inteiros de 0 a 255.</p>
<p>No entanto, inteiros devem representar números <em>negativos e positivos</em>. Com isso, como devemos distribuir igualmente esses inteiros em um único byte?</p>
<p>Empregando uma técnica chamada <strong>complemento de dois</strong>.</p>
<h3>👉 Complemento de dois</h3>
<p>Para distribuir igualmente números inteiros negativos e positivos não fracionários dentro de 8 bits, podemos usar uma técnica chamada <strong>complemento de dois</strong>. Nesta técnica:</p>
<ul>
<li>
<p>o bit mais à esquerda serve como o <strong>bit de sinal</strong>, indicando se o número é positivo ou negativo</p>
</li>
<li>
<p>todos os bits são <em>invertidos</em> ou <em>complementados</em></p>
</li>
<li>
<p>em seguida, <strong>adicionamos 1</strong> ao valor resultante</p>
</li>
</ul>
<p>Desta forma, um único byte representa inteiros que variam de -128 a 127.</p>
<pre><code>2^8 = 256

-127, -126, -125...127, 128
</code></pre>
<h3>👉 Utilizando dois bytes para representar inteiros</h3>
<p>Ao empregar a técnica do complemento de dois, também podemos representar um intervalo de inteiros usando dois bytes (16 bits).</p>
<p>Utilizando o conceito de potências de 2, podemos observar que <strong>dois bytes podem acomodar um total de 65536 valores</strong> diferentes:</p>
<pre><code>2^16 = 65536
</code></pre>
<p>Considerando números negativos, o intervalo se estende de -32768 a 32767, inclusive.</p>
<p>Agora, vamos explorar alguns exemplos utilizando o <strong>PostgreSQL</strong>.</p>
<p>Se você, como eu, é da turma dos <em>containers</em>, ter um PostgreSQL server prontinho pra ser utilizado é mamão com açúcar:</p>
<pre><code class="language-bash">$ docker run --rm -d \
  --name postgres \
  -e POSTGRES_HOST_AUTH_METHOD=trust \
  postgres 
</code></pre>
<p>Em seguida, acesse o terminal <code>psql</code> com o seguinte comando:</p>
<pre><code class="language-bash">$ docker exec -it postgres psql -U postgres
</code></pre>
<blockquote>
<p>O quê você está esperando para ir logo aprender sobre <a href="https://leandronsp.com/articles/thinking-like-containers-3k24">containers</a>?</p>
</blockquote>
<p>No PostgreSQL, o tipo de dado que representa um inteiro de dois bytes é chamado <strong>int2</strong> ou <strong>smallint</strong>:</p>
<pre><code class="language-sql">SELECT 65::int2;
 int2
------
   65
</code></pre>
<p>Para verificar o tipo de dado, podemos usar a função <code>pg_typeof</code>:</p>
<pre><code class="language-sql">SELECT pg_typeof(65::int2);
 pg_typeof
-----------
 smallint
</code></pre>
<p>Como <strong>smallint</strong> usa dois bytes, ele só pode acomodar o intervalo que mencionamos anteriormente em termos de bits e inteiros:</p>
<pre><code class="language-sql">SELECT 32767::int2;
 int2
-------
 32767

SELECT -32767::int2;
 int2
-------
 -32767
</code></pre>
<p>No entanto, se tentarmos exceder o intervalo:</p>
<pre><code class="language-sql">SELECT 32768::int2;
ERROR:  smallint out of range
</code></pre>
<p><em>Incrível, não?</em></p>
<p>Além do <strong>smallint</strong>, Postgres oferece uma variedade de outros tipos de dados inteiros:</p>
<table><thead><tr><th>Tipo de Dado</th><th>Descrição</th><th>Intervalo de Inteiros</th></tr></thead><tbody>
<tr><td>smallint</td><td>Inteiro de dois bytes</td><td>-32.768 a 32.767</td></tr>
<tr><td>integer</td><td>Inteiro de quatro bytes</td><td>-2.147.483.648 a 2.147.483.647</td></tr>
<tr><td>bigint</td><td>Inteiro de oito bytes</td><td>-9.223.372.036.854.775.808 a 9.223.372.036.854.775.807</td></tr>
</tbody></table>
<p>No entanto, todos nós sabemos que o mundo não é apenas composto por inteiros. Inteiros são um subconjunto de um conjunto mais amplo de números chamados <strong>números reais</strong>.</p>
<hr />
<h2>🔵 Bits e outros números reais</h2>
<p><strong>Números reais</strong> podem incluir inteiros, frações e decimais, tanto racionais quanto irracionais.</p>
<p>Por exemplo, <em>3.14159</em> representa o número real <strong>π</strong> (pi), que é um número irracional. É um decimal <em>não repetitivo e não terminante</em>. O valor de π se estende infinitamente sem qualquer padrão em sua representação decimal.</p>
<pre><code>3.14159265358979323846....
</code></pre>
<hr />
<p>Suponha que tenhamos dois bytes (16 bits), que podem representar 65536 inteiros variando de -32768 a 32767.</p>
<p>Quando se trata de representar outros números reais, como decimais, podemos usar uma técnica chamada <strong>ponto fixo</strong> que, apesar de não ser eficiente, pode ser utilizada para fins didáticos neste post.</p>
<hr />
<h2>🔵 Representação de ponto fixo</h2>
<p>Na representação de ponto fixo, dividimos os 16 bits fornecidos em três partes:</p>
<h3>👉 Bit de sinal</h3>
<p>O primeiro bit (mais à esquerda) representa o sinal, sendo 1 para negativo e 0 para positivo.</p>
<h3>👉 Parte decimal</h3>
<p>Os próximos 7 bits representam a parte decimal (fracionária), que pode ter uma precisão de até <code>0.992188</code> em nossa simulação:</p>
<pre><code>2^-7 + 2^-6 + ... + 2^-1 =
0.992188
</code></pre>
<h3>👉 Parte inteira</h3>
<p>Os 8 bits restantes representam a parte inteira, que podem ir de -128 a 127 usando complemento de dois:</p>
<pre><code>complemento_de_dois(
    2^7 + 2^6 + ... + 2^1 = 
    127
)
</code></pre>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/56igr6mlwimkho6vlllk.png" alt="ponto fixo" /></p>
<p>Considerando que a parte inteira, usando 8 bits com complemento de dois, varia de -128 a 127, podemos concluir que, com a <strong>representação de ponto fixo</strong>, os decimais podem variar de <strong>-128.992188 a 127.992188</strong>.</p>
<p>No entanto, essa técnica pode nem sempre ser a mais eficiente.</p>
<p>Portanto, vamos explorar outra técnica para representar decimais. Sim, estamos falando da mundialmente e amplamente utilizada <strong>representação de ponto flutuante</strong>.</p>
<hr />
<h2>🔵 Representação de ponto flutuante</h2>
<p>Tomando como exemplo 16 bits, na representação de ponto flutuante, também dividimos os 16 bits em três grupos:</p>
<h3>👉 Bit de sinal</h3>
<p>O primeiro bit (mais à esquerda) é usado para representar se o número é negativo (1) ou positivo (0).</p>
<h3>👉 Parte do expoente</h3>
<p>A parte do expoente é atribuída aos próximos X bits. Em nossa simulação, vamos alocar 7 bits para esta parte, enquanto utilizamos o <em>primeiro bit do expoente</em> como sendo o <strong>sinal do expoente</strong>.</p>
<p>Assim, a faixa para o expoente se estende de -63 a 63, acomodando valores negativos e positivos:</p>
<pre><code>2^5 + 2^4 + ... 2^1 =
63
</code></pre>
<p><em>Esta parte é crucial para definir a precisão aritmética na representação de ponto flutuante.</em></p>
<h3>👉 Mantissa</h3>
<p>A parte da <strong>mantissa</strong>, também conhecida como <em>significante</em>, usa os 8 bits restantes na nossa simulação, permitindo uma faixa de 1 até 255.</p>
<p><em>Como não estamos representando a parte inteira nesta simulação, não é necessário aplicar complemento de dois à mantissa.</em></p>
<p>🔑 <strong>Agora a parte importante</strong>
Para calcular o maior número de ponto flutuante positivo, multiplicamos a mantissa pelo expoente. É aqui que entra o tal do "ponto flutuante":</p>
<pre><code>mantissa X 2^expoente
</code></pre>
<p>Neste caso, o valor máximo positivo seria obtido multiplicando-se <strong>255 por 2^6</strong>, resultando em um número extremamente grande como <strong>2351959869397967831040.0</strong>.</p>
<p>Por outro lado, o número mínimo maior que zero pode ser representado como 1 multiplicado por <strong>2^-63</strong>, ou <strong>0.00000000000000000010842021724855044340074528008699</strong>.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mof63oaed96584ajqvbg.png" alt="ponto flutuante" /></p>
<p>Observe que esta simulação é uma representação simplificada com precisão limitada e pode não refletir a precisão de formatos de ponto flutuante ideais ou padronizados.</p>
<hr />
<h2>🔵 Problemas e padrões</h2>
<p>De fato, como mencionado anteriormente, <strong>selecionar um número adequado de bits para a parte do expoente</strong> na representação de ponto flutuante é crucial para mitigar problemas de arredondamento e truncamento ao lidar com números fracionários.</p>
<p>Padrões como o <strong>IEEE 754</strong> foram estabelecidos exatamente para abordar essas preocupações e fornecer um framework consistente para a representação de ponto flutuante. O padrão IEEE 754 define o número de bits alocados para o expoente, mantissa e sinal em formatos de precisão simples (32 bits) e dupla precisão (64 bits).</p>
<p>Esses padrões determinam a representação precisa dos vários componentes de um número de ponto flutuante, as <em>regras para operações aritméticas</em> e como lidar com casos excepcionais.</p>
<h3>👉 Precisão simples (4 bytes)</h3>
<p>Os números de <strong>precisão simples</strong> são representados usando 32 bits de memória.</p>
<p>Eles incluem:</p>
<ul>
<li>
<p>1 bit para o sinal do número</p>
</li>
<li>
<p>8 bits para o expoente</p>
</li>
<li>
<p>23 bits para a mantissa</p>
</li>
</ul>
<p>De acordo com os padrões do IEEE, a precisão simples normalmente <strong>manipula de 6 a 9 casas decimais de precisão</strong>.</p>
<h3>👉 Dupla precisão (8 bytes)</h3>
<p>Os números de <strong>dupla precisão</strong> são representados usando 64 bits de memória.</p>
<p>Eles incluem:</p>
<ul>
<li>
<p>1 bit para o sinal do número</p>
</li>
<li>
<p>11 bits para o expoente</p>
</li>
<li>
<p>52 bits para a mantissa</p>
</li>
</ul>
<p>De acordo com os padrões do IEEE, a dupla precisão pode <strong>manipular de 15 a 17 casas decimais de precisão</strong>.</p>
<p><em>Geralmente, a dupla precisão se encaixa melhor quando a alta precisão é necessária, mas esta por sua vez consome mais memória.</em></p>
<hr />
<h2>🔵 Tipos de dados de ponto flutuante</h2>
<p>Muitas linguagens de programação e sistemas de banco de dados aderem aos padrões do IEEE 754, e com o PostgreSQL isto não é exceção.</p>
<p>Vamos ver como o Postgres implementa os tipos de dados de ponto flutuante na prática.</p>
<p>O tipo de dado <strong>float4</strong> segue o padrão de precisão simples do IEEE 754, que aloca 1 bit para o sinal, 8 bits para o expoente e 23 bits para a mantissa:</p>
<pre><code class="language-sql">SELECT 0.3::float4;
 float4
--------
    0.3
</code></pre>
<p>Por outro lado, o tipo de dado <strong>float8</strong> segue o padrão de dupla precisão do IEEE 754, que aloca 1 bit para o sinal, 11 bits para o expoente e 52 bits para a mantissa:</p>
<pre><code class="language-sql">SELECT 0.3::float8;
 float8
--------
    0.3

#####################

SELECT 0.3::float;
 float
--------
    0.3
</code></pre>
<p><em>O tipo <code>float</code> padrão é equivalente à dupla precisão (float8).</em></p>
<hr />
<h2>☣️ Problemas de ponto flutuante na prática</h2>
<p>Vamos mergulhar em cálculos com números de ponto flutuante e ver os <strong>possíveis problemas</strong> na prática.</p>
<p>Considere uma simples soma de <code>0.1 + 0.2</code>:</p>
<pre><code class="language-sql">SELECT 0.1::float + 0.2::float;

 0.30000000000000004
</code></pre>
<p>Este resultado mostra como problemas de precisão podem surgir em números de ponto flutuante de dupla precisão durante operações aritméticas.</p>
<p>Mesmo seguindo padrões, não estamos imunes a esses desafios de cálculo com ponto flutuante.</p>
<p>No entanto, há uma estratégia alternativa que envolve um truque maroto utilizando <strong>inteiros</strong>.</p>
<h3>💡 Um truque com inteiros</h3>
<p>Em vez do tipo de dado <em>float</em>, podemos trabalhar com <strong>inteiros</strong>. Incorporamos um fator multiplicador com base em uma <em>escala decimal</em> ao armazenar valores e, em seguida, dividimos pelo mesmo fator para restaurar a representação decimal original ao recuperar o valor.</p>
<p>Esse método permite cálculos decimais precisos usando inteiros e escala. O fator multiplicador deve ser escolhido com base na precisão decimal necessária.</p>
<p>Para demonstrar, vamos usar esse truque para realizar <code>0.1 + 0.2</code>, com o fator multiplicador <strong>1000</strong>:</p>
<pre><code class="language-sql">SELECT (0.1 * 1000)::int + (0.2 * 1000)::int;

300
</code></pre>
<p>Aqui, cada entrada é multiplicada por <code>1000</code> e convertida para um inteiro. Para recuperar o valor original sem perder a precisão, dividimos por <code>1000</code>:</p>
<pre><code class="language-sql">SELECT (300 / 1000::float);

0.3
</code></pre>
<p><em>Uau, que técnica incrível!</em> 🚀</p>
<p>No entanto, o uso de um fator multiplicador fixo pode ser ineficiente ao lidar com entradas que possuem diferentes casas decimais.</p>
<p>Em vez disso, uma <strong>representação de escala variável</strong> pode ser usada convertendo a entrada em uma string e analisando o número de dígitos decimais, fazendo assim com que o fator multiplicador seja dinâmico para cada número real.</p>
<p>Mas tenha cuidado, representações decimais de escala variável exigem <strong>manipulação cuidadosa de cálculos complexos</strong>, escala decimal precisa e várias outras sutilezas da aritmética decimal, que não é tão trivial.</p>
<p>É aqui que entram os <strong>decimais</strong>.</p>
<hr />
<h2>🔵 Decimais ao resgate</h2>
<p>Decimais lidam com os desafios associados a cálculos aritméticos complexos envolvendo decimais. Ao passo em que eles <em>reduzem significativamente</em> os problemas de precisão comumente encontrados em números de ponto flutuante.</p>
<p>Diversas linguagens de programação e sistemas de banco de dados implementam decimais. PostgreSQL oferece o tipo de dado <strong>decimal</strong>, que oferece uma precisão superior em comparação com floats.</p>
<pre><code class="language-sql">SELECT 0.1::decimal + 0.2::decimal;
0.3
</code></pre>
<p>Os decimais também podem ser configurados para <strong>precisão e escala arbitrárias</strong>:</p>
<pre><code class="language-sql"># Exemplo: aceita números de até 999.99
SELECT 0.1::decimal(5, 2);
0.10

SELECT 999.99::decimal(5, 2);
999.99
</code></pre>
<p>Convenientemente, o tipo de dado padrão para decimais no PostgreSQL é <strong>numeric</strong>, que é idêntico a <em>decimal</em>:</p>
<pre><code class="language-sql">SELECT pg_typeof(0.1);

numeric
</code></pre>
<hr />
<h2>⚠️ Cuidado com o arredondamento</h2>
<p>Arredondar números decimais programaticamente pode levar a resultados imprecisos. Por exemplo, a soma <code>25.986 + -0.4125 + -25.5735</code> teoricamente deveria resultar em zero:</p>
<pre><code class="language-sql">SELECT 25.986 + -0.4125 + -25.5735;

0.0000
</code></pre>
<p>Vamos ilustrar como podemos <strong>arredondar apenas a soma final</strong> para duas casas decimais:</p>
<pre><code class="language-sql">SELECT ROUND(25.986 + -0.4125 + -25.5735, 2);

0.00
</code></pre>
<p><em>So far, so good</em>. Tudo funcionando como esperado.</p>
<p>Com tipos de dados adequados, como decimais, o problema aritmético inerente aos números de ponto flutuante já é resolvido.</p>
<p>Todavia, o <strong>arredondamento introduz seu próprio conjunto de desafios</strong>. Mesmo que os decimais sejam excelentes para a precisão e aritmética de dados decimais, as operações de arredondamento envolvem <em>algum grau de aproximação</em>.</p>
<p>Para simular um problema com arredondamentos desnecessários, vamos arredondar cada número decimal antes de somá-los:</p>
<pre><code class="language-sql">SELECT ROUND(25.986, 2) + ROUND(-0.4125, 2) + ROUND(-25.5735, 2);

0.01
</code></pre>
<p><em>OMG e agora?</em> 😭</p>
<p>Cada vez que arredondamos um número, estamos adicionando um pouco de imprecisão. <em>Bit a bit</em>, o resultado final pode ficar longe do esperado, <strong>pois a memória do computador é finita</strong> e não é possível representar todas as casas decimais possíveis resultantes de uma aritmética arbitrária de números reais.</p>
<blockquote>
<p>Lembra do resultado da <code>mantissa X 2^expoente</code>?
Pois então...</p>
</blockquote>
<p>Esses exemplos destacam por que <strong>o arredondamento desnecessário deve ser evitado</strong>. Como o arredondamento é uma aproximação, é melhor adiá-lo até a etapa final, ou seja, apenas quando formos <em>apresentar os dados ao usuário final</em>.</p>
<hr />
<h2>Decimais em outras tecnologias</h2>
<p>Cada linguagem de programação ou ferramenta possui seu próprio tipo de dados para lidar com precisão arbitrária, como os decimais do PostgreSQL.</p>
<p>Ruby oferece a classe <a href="https://ruby-doc.org/stdlib-2.5.1/libdoc/bigdecimal/rdoc/BigDecimal.html">BigDecimal</a>, que facilita a aritmética decimal de ponto flutuante de precisão arbitrária.</p>
<p>Da mesma forma, Java também inclui uma <a href="https://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html">classe BigDecimal</a> para este mesmo propósito.</p>
<p>Golang também não é exceção. Ela possui <a href="https://go.dev/src/math/big/decimal.go">aritmética decimal de precisão arbitrária</a> que resolve os mesmos problemas.</p>
<p>É crucial verificar se a tecnologia que você está usando oferece suporte a precisão arbitrária como decimais. Se você precisar de uma precisão maior, estas soluções costumam ser mais adequadas do que o uso de números de ponto flutuante brutos.</p>
<p>Ao limite, se precisão for algo crítico para teu negócio e a tecnologia utilizada <em>não fornece tipos como os big decimals</em> de precisão arbitrária, prefira então utilizar <strong>números inteiros com fator multiplicador (100, 1000, 10000, etc)</strong> que contemple as casas decimais suficientes para a precisão necessária.</p>
<hr />
<h2>Conclusão</h2>
<p>Neste post, exploramos as complexidades dos números de <strong>ponto flutuante</strong>.</p>
<p>Investigamos também como os computadores compreendem informações por meio do <strong>sistema binário</strong>, desde a representação de inteiros e a ineficiência da representação de ponto fixo para decimais, até chegar aos números de ponto flutuante e suas <em>limitações</em>.</p>
<p>Além disso, discutimos como os tipos de dados de precisão arbitrária, como os <strong>decimais</strong>, abordam esses problemas de precisão inerentes aos pontos flutuantes.</p>
<p>Por fim, discutimos e compartilhamos as melhores práticas para lidar com problemas de <em>arredondamento</em> de números decimais.</p>
<p>Espero que esses tópicos tenham sido apresentados de forma didática, tornando os problemas de ponto flutuante não mais um problema!</p>
<p><em>Cheers!</em></p>
<hr />
<h2>Referências</h2>
<p>https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
https://www.postgresql.org/docs/current/datatype.html
https://en.wikipedia.org/wiki/IEEE_754
https://www.doc.ic.ac.uk/~eedwards/compsys/float/
https://en.wikipedia.org/wiki/Floating-point_error_mitigation
https://en.wikipedia.org/wiki/Single-precision_floating-point_format
https://en.wikipedia.org/wiki/Double-precision_floating-point_format
https://en.wikipedia.org/wiki/Decimal_floating_point</p>
]]></description>
<pubDate>2023-06-06</pubDate>
</item>
</channel></rss>
