🎵 last played track: waiting for a response from server... (JavaScript is required) 🎵

contacts

you can message me on telegram or mastodon

> main page
> posts section

and also you can subscribe to my telegram channel with pictures!

Как же все таки изменить байты строки в Go?

Просто захотелось чуть чуть поиграться с пакетом unsafe в Go.

Строки (тип string) в Go являются immutable, то есть изменять их нельзя. Ну вообще конечно можно, но не напрямую.

Строка в Go под капотом является структурой вида: указатель на данные, длина данных. И первое, что приходит в голову чтобы изменить строку - добраться до поля с указателем, прибавить к нему индекс байта который надо поменять, разыменовать полученный адрес и что то ему присвоить.

Но в реальности все не так просто и при попытке что то положить по вычисленному адресу программа упадет с segmentation fault (SIGSEGV). Чтобы этого избежать, предварительно надо выдать права на запись в страничку памяти где находится целевая строка. Сделать это можно через системные вызовы.

package main

import (
	"fmt"
	"syscall"
	"unsafe"
)

func main() {
	s := "hello"
	fmt.Println("Original:", s)

	// Строки в Go - структуры с двумя полями: указатель на данные и длина
	// тут строка интерпретируется как структура
	strHeader := (*struct {
		data unsafe.Pointer
		len  int
	})(unsafe.Pointer(&s))

	// Вычисляем начало страницы памяти
	pageSize := syscall.Getpagesize()
	pageStart := uintptr(strHeader.data) - (uintptr(strHeader.data) % uintptr(pageSize))
	fmt.Printf(
		"\nАдрес строки = %d\nразмер страницы памяти = %d\nадрес строки относительно начала страницы = %d\nадрес страницы = %d\n\n",
		uintptr(strHeader.data),
		uintptr(pageSize),
		(uintptr(strHeader.data) % uintptr(pageSize)),
		pageStart)

	// Имея адрес страницы и ее размер даю права на запись на данной странице памяти
	ret, _, err := syscall.Syscall(
		syscall.SYS_MPROTECT,
		pageStart,
		uintptr(pageSize),
		uintptr(syscall.PROT_READ|syscall.PROT_WRITE),
	)
	if ret != 0 {
		panic("mprotect failed: " + err.Error())
	}

	// Теперь можно менять строку
	// поменяю, например, пятый байт
	fifthBytePtr := (*byte)(unsafe.Pointer(uintptr(strHeader.data) + 4))
	*fifthBytePtr = 0

	// Восстанавливаю дефолтные права
	ret, _, err = syscall.Syscall(
		syscall.SYS_MPROTECT,
		pageStart,
		uintptr(pageSize),
		uintptr(syscall.PROT_READ),
	)
	if ret != 0 {
		panic("mprotect restore failed: " + err.Error())
	}

	fmt.Println("Modified:", s) // Должно быть "hell"
}

Тестил на go version go1.22.2 linux/amd64

mod time: 1746050393