Writing immediates to memory
An immediate in assembly is a scalar value such as 123.
While the concept is familiar from high-level languages, immediates require more attention in x86-64 assembly.
There are no floating-point immediates, so everything below is about integers.
Memory access
Writing 123 immediate value to a memory address like so mov [rdi], 123 can catch us off guard.
﹩ cat > demo.s <<ASM
.intel_syntax noprefix
.text
.global _demo
_demo:
mov [rdi], 123
ret
ASM
﹩ zig build-obj demo.s
error(compilation): clang failed with stderr: demo.s:5:9: error: ambiguous operand size for instruction 'mov'
mov [rdi], 123
^~~~
The LLVM assembler behind zig build-obj tells us that our mov instruction has an ambiguous operand size
(if the example seems confusing, try my earlier post on using Assembly with Zig).
Indeed, the assembler is not sure how many bytes to use to represent 123 in memory.
It can use a byte, a word, a double word, and so on.
Interestingly, NASM v3.02 defaults to using a byte size and truncates larger values with a warning.
﹩ cat > demo.asm <<ASM
section .text
global _demo
_demo:
mov [rdi], 123
ret
ASM
﹩ nasm -f macho64 demo.asm
﹩ objdump -d --x86-asm-syntax=intel demo.o
0: c6 07 7b mov byte ptr [rdi], 0x7b
3: c3 ret
Let's see how our mov [rdi], 123 is different from disassembled mov byte ptr [rdi], 0x7b:
byte ptr in front of [rdi] means to treat the memory address in register rdi
as a pointer to a byte size integer, e.g., i8 or u8
0x7b is 123 in hex
The LLVM assembler has no problems building the object file demo.o once we add byte ptr.
The objdump confirms that zig build-obj emits the same machine code for the _demo() function as NASM.
﹩ cat > demo.s <<ASM
.intel_syntax noprefix
.text
.global _demo
_demo:
mov byte ptr [rdi], 123
ret
ASM
﹩ zig build-obj demo.s
﹩ objdump -d --x86-asm-syntax=intel demo.o
0: c6 07 7b mov byte ptr [rdi], 0x7b
3: c3 ret
We've got the function in demo.o file, let's make sure it actually works.
Here is a Zig program that declares a byte size variable var v: i8 = 0
and passes its pointer when calling demo(&v).
The assembly function _demo() writes 123 to that dereferenced pointer and returns.
Finally, we print the variable v and its raw memory with std.mem.asBytes(&v).
If you're unsure how it all works, see The C ABI: Using Assembly with Zig post.
﹩ cat > main.zig <<ZIG
const std = @import("std");
extern fn demo(v: *i8) void;
pub fn main() void {
var v: i8 = 0;
demo(&v);
std.debug.print("{d} is stored as 0x{x} at {*}\n", .{
v,
std.mem.asBytes(&v),
&v,
});
}
ZIG
﹩ zig build-exe main.zig demo.s
﹩ ./main
123 is stored as 0x7b at i8@7ff7b00a2000
As expected, the program's output shows that 123 was stored as 0x7b.
It also printed the memory address 0x7ff7b00a2000 where the value was placed.
What if we wanted to use 4 bytes of memory instead of just one byte?
We would need to replace byte with dword in the mov instruction like this mov dword ptr [rdi], 123,
and update main.zig to use i32 instead of i8.
🔻 mov dword ptr [rdi], 123
﹩ cat > demo.s <<ASM
.intel_syntax noprefix
.text
.global _demo
_demo:
mov dword ptr [rdi], 123
ret
ASM
﹩ cat > main.zig <<ZIG
const std = @import("std");
extern fn demo(v: *i32) void;
pub fn main() void {
var v: i32 = 0;
demo(&v);
std.debug.print("{d} is stored as 0x{x} at {*}\n", .{
v,
std.mem.asBytes(&v),
&v,
});
}
ZIG
﹩ zig build-exe main.zig demo.s
﹩ ./main
123 is stored as 0x7b000000 at i32@7ff7b4fe9000
The updated program reported that it stored 0x7b as 0x7b000000 (not 0x0000007b) at 0x7ff7b4fe9000 address.
This is because 123 immediate's bytes 00 00 00 7b are arranged in little-endian order 7b 00 00 00:
the least significant byte 7b is placed at the smallest memory address 7ff7b4fe9000.
Address Byte
7ff7b4fe9003 00 ← most significant byte
7ff7b4fe9002 00
7ff7b4fe9001 00
7ff7b4fe9000 7b ← least significant byte
Two's complement
What if we store a negative immediate value mov byte ptr [rdi], -123?
🔻 mov byte ptr [rdi], -123
﹩ cat > demo.s <<ASM
.intel_syntax noprefix
.text
.global _demo
_demo:
mov byte ptr [rdi], -123
ret
ASM
﹩ cat > main.zig <<ZIG
const std = @import("std");
extern fn demo(v: *i8) void;
pub fn main() void {
var v: i8 = 0;
demo(&v);
std.debug.print("{d} is stored as 0x{x} at {*}\n", .{
v,
std.mem.asBytes(&v),
&v,
});
}
ZIG
﹩ zig build-exe main.zig demo.s
﹩ ./main
-123 is stored as 0x85 at i8@7ff7b8653000
The -123 value got stored as 0x85 in memory.
This is due to two's complement
method of representing a signed integer (invert all bits and add one):
0111 1011 = 0x7b -- 123 in binary format
1000 0100 = 0x84 -- invert all bits of 123
1000 0101 = 0x85 -- add one
The Python one liner below confirms it.
﹩ python3 -c 'print("0x7b = {0:0>8b}\n0x85 = {1:0>8b}".format(0x7b, 0x85))'
0x7b = 01111011
0x85 = 10000101
If we store -123 as four bytes mov dword ptr [rdi], -123, we'll get 0x85ffffff memory representation.
🔻 mov dword ptr [rdi], -123
﹩ cat > demo.s <<ASM
.intel_syntax noprefix
.text
.global _demo
_demo:
mov dword ptr [rdi], -123
ret
ASM
﹩ cat > main.zig <<ZIG
const std = @import("std");
extern fn demo(v: *i32) void;
pub fn main() void {
var v: i32 = 0;
demo(&v);
std.debug.print("{d} is stored as 0x{x} at {*}\n", .{
v,
std.mem.asBytes(&v),
&v,
});
}
ZIG
﹩ zig build-exe main.zig demo.s
﹩ ./main
-123 is stored as 0x85ffffff at i32@7ff7b2e8a00c
Converting 0x85ffffff to a big-endian order gives us 0xffffff85:
0000 0000 0000 0000 0000 0000 0111 1011 = 0x7b -- 123 in binary format
1111 1111 1111 1111 1111 1111 1000 0100 = 0xffffff84 -- invert all bits of 123
1111 1111 1111 1111 1111 1111 1000 0101 = 0xffffff85 -- add one
﹩ python3 -c 'print("0xffffff85 = {0:0>32b}".format(0xffffff85))'
0xffffff85 = 11111111111111111111111110000101
Sign extension
How about storing a positive number 2,147,483,648 (2^31) as eight bytes mov qword ptr [rdi], 2147483648?
﹩ cat > demo.s <<ASM
.intel_syntax noprefix
.text
.global _demo
_demo:
mov qword ptr [rdi], 2147483648
ret
ASM
﹩ cat > main.zig <<ZIG
const std = @import("std");
extern fn demo(v: *i64) void;
pub fn main() void {
var v: i64 = 0;
demo(&v);
std.debug.print("{d} is stored as 0x{x} at {*}\n", .{
v,
std.mem.asBytes(&v),
&v,
});
}
ZIG
﹩ zig build-exe main.zig demo.s
error(compilation): clang failed with stderr: demo.s:5:5: error: invalid operand for instruction
mov qword ptr [rdi], 2147483648
^
Zig tooling straight up refuses to build with invalid operand for instruction error,
and NASM only warns signed dword exceeds bounds.
﹩ cat > demo.asm <<ASM
section .text
global _demo
_demo:
mov qword [rdi], 2147483648
ret
ASM
﹩ nasm -f macho64 demo.asm
demo.asm:4: warning: signed dword exceeds bounds [-w+number-overflow]
﹩ zig build-exe main.zig demo.o
﹩ ./main
-2147483648 is stored as 0x00000080ffffffff at i64@7ff7bf1ff008
The program's output is surprising -2147483648 is stored as 0x00000080ffffffff:
-2147483648 stored value is negative
0x00000080ffffffff memory representation has four 0xff bytes
whereas we expect four zeros 0x0000008000000000
Let's transform 0x00000080ffffffff to a big-endian order 0xffffffff80000000:
0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 = 00 00 00 00 80 00 00 00 -- 2147483648 in binary format
1111 1111 1111 1111 1111 1111 1111 1111 0111 1111 1111 1111 1111 1111 1111 1111 = ff ff ff ff 7f ff ff ff -- invert all bits of 2147483648
1111 1111 1111 1111 1111 1111 1111 1111 1000 0000 0000 0000 0000 0000 0000 0000 = ff ff ff ff 80 00 00 00 -- add one
﹩ python3 -c 'print("0xffffffff80000000 = {0:0>64b}".format(0xffffffff80000000))'
0xffffffff80000000 = 1111111111111111111111111111111110000000000000000000000000000000
Let's look at the machine code NASM produced.
﹩ objdump -d --x86-asm-syntax=intel demo.o
0: 48 c7 07 00 00 00 80 mov qword ptr [rdi], -0x80000000
7: c3 ret
The machine code shows that despite our 8-byte memory destination,
the immediate 00 00 00 80 (in little-endian form) is only 4 bytes.
The mov instruction in x86-64 can't write a 64-bit immediate to memory,
so the CPU sign-extends those 4 bytes to fill the 8.
Our 2147483648 is 0x80000000 and its bit 31 is set, so the CPU fills the upper 32 bits with ones
and writes 0xffffffff80000000 which is -2147483648.
The solution is to write to a register first, and then to memory.
﹩ cat > demo.asm <<ASM
section .text
global _demo
_demo:
mov rax, 2147483648
mov [rdi], rax
ret
ASM
﹩ nasm -f macho64 demo.asm
﹩ zig build-exe main.zig demo.o
﹩ ./main
2147483648 is stored as 0x0000008000000000 at i64@7ff7b8227008
Note that no size specifier is needed now because the register rax already tells the assembler
that the store is eight bytes wide.
Only the register form of mov accepts a 64-bit immediate, though NASM doesn't even need it here.
﹩ objdump -d --x86-asm-syntax=intel demo.o
0: b8 00 00 00 80 mov eax, 0x80000000
5: 48 89 07 mov qword ptr [rdi], rax
8: c3 ret
NASM replaced our mov rax, 2147483648 with mov eax, 0x80000000
which puts 0x0000008000000000 into rax because writing to a 32-bit register eax
zero-extends (not sign-extends!) into the full 64-bit register rax.
Note that this is specific to 32-bit writes: ax and al leave the upper bits of rax untouched.
The same 4-byte immediate limit also applies to add, cmp, and the other instructions.
Anything larger has to go through a register.
References:
comments
Zig SIMD in assembly
In the C ABI post we saw how
convenient it's to write Intel-style assembly code in Zig.
This time let's see what it takes to use SIMD in assembly.
Assembly side
The first SIMD example
in Modern x86 Assembly book demonstrates 16-bit integers addition
using both wraparound and saturated arithmetic …
comments
Read More
The C ABI: Using Assembly with Zig
I am currently reading Modern x86 Assembly Language Programming by Daniel Kusswurm.
The book provides examples in C++, NASM, and MASM,
but since I'm more interested in Zig, I’ve decided to follow along using it as my primary language.
As we'll see in this post, the C ABI (Application …
comments
Read More
Go archsimd preview
In the previous post
we've implemented SIMD sum in Go assembly [1, 2, 3, 4] + [5, 6, 7, 8].
This is going to be much easier to do in Go 1.26 because of
simd/archsimd package,
see #73787 proposal.
So far the package provides access to amd64-specific SIMD operations …
comments
Read More
Intro to SIMD in avo
In the previous post we wrote a Hello World in avo.
Let's do something practical this time, e.g., related to performance
since we go into all this trouble of writing Go assembly.
You can find the code examples in
github.com/marselester/misc.
Processing more data in a single …
comments
Read More
Hello World in avo 🥑
Let's learn together how to write some Go assembly using avo
aka writing assembly-like Go code to generate assembly.
To make it more clear, here is an avo program add/asm.go.
package main
import asm "github.com/mmcloughlin/avo/build"
func main() {
asm.TEXT("Add", asm.NOSPLIT, "func(x …
comments
Read More
DIY CPU profiler: position independent executable 🥧
In the simplest case of symbolization
we used -fno-pie -no-pie flags, so
gcc wouldn't produce a position independent executable (PIE).
It is produced by default for security measures such as
address space layout randomization (ASLR).
That means each time the program runs,
its segments are loaded into different regions of …
comments
Read More
DIY CPU profiler: the simplest case of symbolization
In the BPF maps to pprof post
we managed to collect CPU samples and store them in pprof format.
Here I would like to explore the simplest case of symbolization
and put shared libraries aside.
Symbolization is resolving sampled memory addresses to function names (symbols).
In our case, simply searching …
comments
Read More
Linux process
Being curious about BPF, I studied source code of several programs from the BCC libbpf-tools.
BPF performance tools book aided me to navigate BPF C code.
For example, it explained that a BPF program has to use helpers because it can't access arbitrary memory (outside of BPF) and can't call …
comments
Read More
Bandwitch 🧙♀️ of CPU and storages
Let us begin with a definition of a CPU clock rate ⏰.
It refers to the frequency at which the clock generator (an oscillator crystal)
of a processor can generate pulses, which are used to synchronize the operations of its components.
For example, 1GHz CPU implies that its clock runs at …
comments
Read More
DIY CPU profiler: from BPF maps to pprof
In previous post
I had a DIY BPF profiler printing stack trace IDs of a given process,
though they were not very helpful.
﹩ sudo go run ./cmd/profiler/ -pid 15958
Waiting for stack traces...
{PID:15958 UserStackID:132 KernelStackID:114} seen 1 times
Let's try to show more useful information …
comments
Read More
Continuous profiling in Go
In this post I explore possibilities of continuous profiling of Go programs
using Parca and also peek under the hood of its BPF agent.
You can find the results of my experiments in
github.com/marselester/diy-parca-agent.
Ad hoc profiling
I was looking for a way to profile Go programs …
comments
Read More
BPF Go program in Kubernetes
BPF opens a lot of possibilities of making observability tools running in Kubernetes.
One can start with BCC libbpf-tools written in C,
e.g., launch tcpconnlat program and process its stdout with another program to
detect cases when it took too long to establish a TCP connection.
For example, curl …
comments
Read More
BPF: Go frontend for tcpconnect
In the previos BPF post
I shared my experiment of writing Go frontend for execsnoop.
Here I would like to focus on tcpconnect, a BCC tool to trace new TCP active connections.
It is useful for determining who is connecting to whom.
This works by tracing the tcp_v4_connect() and tcp_v6_connect …
comments
Read More
BPF: Go frontend for execsnoop
After reading Brendan Gregg's books about BPF I was excited to try and implement something in Go.
At a first glance it turned out I would need to install LLVM, Clang, and kernel header dependencies to run a simple program.
Fortunately BTF, CO-RE technologies
eliminate those dependencies though kernel >=5 …
comments
Read More
Ambassador as API Gateway
API gateway
acts as a reverse proxy, routing API requests from clients to services.
Usually it also performs authentication and rate limiting, so the services behind the gate don't have to.
In this short tutorial we'll see how to achieve that with Ambassador.
The demo is based on a dummy …
comments
Read More
Traefik as API Gateway
API gateway
acts as a reverse proxy, routing API requests from clients to services.
Usually it also performs authentication and rate limiting, so the services behind the gate don't have to.
In this short tutorial we'll see how to achieve that with Traefik reverse-proxy.
The demo is based on a …
comments
Read More
How to Structure Go Projects
I came to Go from Django where the framework defines project layout, thus I wanted to know
how to structure my Go applications. After reading documentation and building a few Django projects,
you get a clear mental picture, as most of the questions are already answered.
That helps to keep …
comments
Read More
Forward DogStatsD Metrics to Prometheus
tl;dr: StatsD doesn't have metric labels, DogStatsD does.
This is a follow up post after
Instrumenting Django with Prometheus and StatsD.
You got Prometheus up and running and eager to start instrumenting your Django application.
Don't be hasty and read Prometheus Best Practices.
Let's say our application has to …
comments
Read More
Instrumenting Django with Prometheus and StatsD
If you ever wondered how to monitor your Django application with Prometheus this article is for you.
Quick search on the topic will lead you to django-prometheus.
For those who don't want to use it, there is another way to export application metrics
via StatsD.
The idea is to send …
comments
Read More
Minukube & Amazon EC2 Container Registry
Minukube is an easy way to run Kubernetes locally.
When we want to build a Docker image in Minukube (so Kubernetes has an access to it),
we can configure our Docker client to communicate with the Minikube Docker daemon.
$ minikube start
Starting local Kubernetes cluster...
Kubectl is now configured to …
comments
Read More
Prometheus on Kubernetes
Prometheus is a monitoring toolkit.
Let's set it up on Kubernetes and test how it works by scraping HTTP request metrics
from hello web application
which also runs in the same cluster.
First of all, we need Kubernetes cluster running. It's easy to bootstrap one via Google Container Engine.
comments
Read More
Django REST framework: pagination on PostgreSQL triggers
Django and Django REST Framework use SQL COUNT in pagination.
As your database grows SQL COUNT becomes too slow. Fortunately the frameworks
are well designed and allow to customize a way items are count.
Let me illustrate that on a typical "books" example.
class Author(models.Model):
name = models.CharField …
comments
Read More
API based on Flask
Here I want to consider implementation of API best practices which
usually don't follow Fielding's REST strictly. Example Flask project
is on GitHub.
API Versioning
Interfaces are changed hence versioning is mandatory in order to not annoy
your users. You might need to add new resource or field to particular …
comments
Read More
Slides about SaltStack
Update I gave a talk with Simon Robson at Beercamp in Chiang Mai,
Thailand on 12 Dec 2013. We compared Salt and Ansible.
Here are my slides.
comments
Read More
Developing & Deploying Django project with SaltStack
Eventually you will need to deploy project,
but deployment was not considered in the previous post. Let's find it out.
Server configuration is different from local, thus environments will be needed
(at least production prod and development dev). Salt uses base
environment by default.
Environments are set in minion.conf …
comments
Read More
Developing Django project with SaltStack
Let's use Messaging System as an example of Django project. I want it to
run in VirtualBox which is managed by Vagrant. Infrastructure management
is provided by SaltStack.
I advise you to create separate folder for repositories (currently there
is only one) of project and clone Messaging System there.
Also …
comments
Read More
Preparation to Python Interview
I decided to collect a little more information and experience during
preparation to Python developer interview. These are some information and
links which seemed important to me. Maybe it will be helpful.
How does it usually go?
What kind of projects did you participate in?
What did you do at …
comments
Read More
Django TODO: тестирование во время конструирования
Тестирование, выполняемое разработчиками -- один из важнейших элементов полной
стратегии тестирования.
Тестирование может указать только на отдельные дефектные области программы --
оно не сделает программу удобнее в использовании, более быстрой, компактной,
удобочитаемой или расширяемой.
Цель тестирования противоположна целям других этапов разработки. Его целью
является нахождение ошибок. Успешным считается тест, нарушающий работу ПО …
comments
Read More
Django TODO: конструирование системы
При работе над проектом конструирование включает другие процессы, в том числе
проектирование. Формальная архитектура дает ответы только на вопросы
системного уровня, при этом значительная часть проектирования может быть
намеренно оставлена на этап конструирования. Проектирование -- это
"постепенный" процесс. Проекты приложений не возникают в умах разработчиков
сразу в готовом виде. Они развиваются …
comments
Read More
Django TODO: проектирование архитектуры системы
Следующим этапом разработки системы является проектирование архитектуры.
Архитектура должна быть продуманным концептуальным целым. Главный тезис самой
популярной книги по разработке ПО "Мифический человеко-месяц" гласит, что
основной проблемой, характерной для крупных систем, является поддержание их
концептуальной целостности. Хорошая архитектура должна соответствовать
проблеме .
Разделение системы на подсистемы на уровне архитектуры, позволяет …
comments
Read More
Django TODO: выработка требований к системе
После прочтения Макконелла захотелось спроецировать его советы на Django.
Для этого я взял за основу разработку системы Django TODO. Итак, первый этап -- выработка требований к
системе.
Требования подробно описывают, что должна делать система. Внимание к
требованиям помогает свести к минимуму изменения системы после начала
разработки. Явные требования помогают гарантировать, что …
comments
Read More
Соглашения по разработке на Python/Django
Во время разработки я часто сверяюсь с известными мне соглашениями,
стараюсь следовать рекомендациям. Цитировать их не имеет смысла -- лучше
приведу ссылки.
PEP 8 -- Style Guide for Python Code.
Code Like a Pythonista: Idiomatic Python.
В нем я нашел ответы на вопросы форматирования длинных строк:
expended_time = (self.finish_date() - self.start_date
+ datetime …
comments
Read More
Разделение настроек в Django
В Django wiki собраны
различные способы разделения настроек. Мне нравится вариант, описанный в блоге
Senko Rašić:
settings/
├── __init__.py
├── base.py
├── development.py
├── local.py
└── production.py
base.py содержит общие настройки для development.py и
production.py, например:
ADMINS = ()
MANAGERS = ADMINS
TIME_ZONE = 'Asia/Yekaterinburg'
# ...
production.py содержит настройки для …
comments
Read More
Краткий обзор инфраструктуры для разработки reusable Django приложений
Начиная впервые разрабатывать веб-приложения на новом фреймворке программист
зачастую сталкивается с некоторыми трудностями. При разработке отчуждаемых
веб-приложений на Django к этим проблемам необходимо отнести организацию
файлов в проекте, обнаружение тестов, вопросы пакетирования приложений и
организации автоматизированного тестирования. В данной статье приведены пути
решения этих проблем.
Важно знать различия между двумя …
comments
Read More
Вычислительные методы одномерной оптимизации
На третьем курсе по предмету методы оптимизации делали лабораторную работу на
тему «Вычислительные методы одномерной оптимизации».
Задача заключалась в поиске безусловного минимума функции
f(x) = pow(x, 3) – x + pow(e, -x) на начальном интервале [0, 1]
с точностью 0.00001.
Вычисления производились через:
- пассивный метод;
- равномерные блочные методы;
- метод …
comments
Read More
Определение нажатия комбинации клавиш средствами BIOS на ассемблере
По учебе понадобилось написать программу на ассемблере, которая должна
распознать нажатие «горячей» комбинации клавиш LeftCtrl+RightShift+F3 и
реагировать на него звуковым сигналом. Информации/примеров по этой теме
маловато, по этому решил опубликовать свою программку.
masm
.model small
.stack 256
.data
Msg_about db 'Распознать нажатие «горячей» комбинации клавиш', 0Ah, 0Dh …
comments
Read More
Моделирование одноканальной СМО с отказами
Дана одноканальная система массового обслуживания с отказами. В нее поступают
заявки через промежуток времени n, где n – случайная величина,
подчиненная равномерному закону распределения. Время обслуживания заявки
системой m также является случайной величиной с показательным законом
распределения. Если к моменту прихода заявки канал занят, заявка покидает
систему необслуженной.
Изначально код был …
comments
Read More