New FAMILUG

The PyMiers

Tuesday, 31 January 2023

[Go] Đôi khi 0.1 + 0.1 + 0.1 == 0.3

Trong cuộc sống, những thứ đúng là đúng, sai là sai thật dễ dàng, còn những thứ đôi khi thế này, đôi khi thế nọ, thì tùy.

Như các học viên của https://pymi.vn đều biết vì sao 0.1 + 0.1 + 0.1 != 0.3 trong Python và hầu hết các ngôn ngữ khác, thì với Go, thực hiện 1 phép tính cộng đơn giản theo 2 cách khác nhau, cho ra 2 kết quả khác nhau.

go nowhere const

Cách giống các ngôn ngữ khác

func main() {
    x := 0.1
    fmt.Printf("%v + %v + %v == 0.3? %t\n", x, x, x, x+x+x == 0.3)
    fmt.Printf("%f\n", x+x+x)
    fmt.Printf("%.17f\n", x+x+x)
}

Kết quả giống như Python và các ngôn ngữ khác, 0.1 + 0.1 + 0.1 != 0.3 do kết quả vế trái là 0.30000000000000004

0.1 + 0.1 + 0.1 == 0.3? false
0.300000
0.30000000000000004

https://go.dev/play/p/eDe3szlFyi7

Cách của riêng Go

Nếu viết khác đi một chút, sử dụng từ khóa const, hay viết trực tiếp mà không gán cho biến x, kết quả lại là đúng:

func main() {
    const x = 0.1
    fmt.Printf("%v + %v + %v == 0.3? %t\n", x, x, x, x+x+x == 0.3)

    fmt.Printf("%v + %v + %v == 0.3? %t\n", 0.1, 0.1, 0.1, 0.1+0.1+0.1 == 0.3)
    fmt.Printf("%.30f\n", 0.1+0.1+0.1)
    fmt.Printf("%.30f\n", 0.3)
}

Kết quả:

0.1 + 0.1 + 0.1 == 0.3? true
0.1 + 0.1 + 0.1 == 0.3? true
0.299999999999999988897769753748
0.299999999999999988897769753748

https://go.dev/play/p/IPuQD9aLlV8

Vậy rốt cuộc là sao?

Go const

Từ khóa const trong Go khác với trong các ngôn ngữ khác, không phải mỗi chuyện nó immutable (không thay đổi), mà cách tính toán giá trị cũng không như "thường".

Cách viết 0.1+0.1+0.1 cũng là const trong Go, gọi chính xác là const expression.

Sự khác biệt to lớn này khiến các tác giả Go phải viết một bài blog để giải thích const khác các ngôn ngữ thế nào https://go.dev/blog/constants.

Trích blog nói trên:

First, a quick definition. In Go, const is a keyword introducing a name for a scalar value such as 2 or 3.14159 or "scrumptious". Such values, named or otherwise, are called constants in Go. Constants can also be created by expressions built from constants, such as 2+3 or 2+3i or math.Pi/2 or ("go"+"pher").

Khi viết 0.1 trong Go, giá trị này không có kiểu (have no type), hay còn gọi là untyped constant. Nhưng nó có kiểu mặc định (default type) KHI CẦN. Hoặc cũng có thể đổi sang một kiểu khác. Trong Go có 2 kiểu float là float32 và float64.

    const x = 0.1
    fmt.Printf("%T\n", x) // float64
    var y float32 = 0.2
    fmt.Printf("%T\n", x+y) // float32
    z := 0.1
    fmt.Printf("%T\n", z+y) // Compile error: invalid operation: z + y (mismatched types float64 and float32)

x có default type là float64, khi cộng với giá trị y kiểu float32, x lúc này có kiểu float32 mà không cần phải đổi kiểu.

  • Chú ý, const x = 0.1 (untyped) khác với z := 0.1 (biến z kiểu float64).
  • Chú ý, const x float32 = 0.1 đã có kiểu float32, khác với untyped const.

Tính toán const expression

Trong bài blog:

Numeric constants live in an arbitrary-precision numeric space; they are just regular numbers.

    const Huge = 1e1000
    fmt.Println(Huge / 1e999)

in ra 10.

Các const được tính toán bởi Go compiler - khi build, chứ không tính KHI CHẠY, sử dụng kiểu dữ liệu có độ chính xác cao hơn so với float64

Trong Go spec https://go.dev/ref/spec#Constants

Numeric constants represent exact values of arbitrary precision and do not overflow. Implementation restriction: Although numeric constants have arbitrary precision in the language, a compiler may implement them using an internal representation with limited precision. That said, every implementation must: Represent floating-point constants, including the parts of a complex constant, with a mantissa of at least 256 bits and a signed binary exponent of at least 16 bits.

Vậy khi tính toán 0.1+0.1+0.1 với 256 bits (so với float64 chỉ có 64 bit), độ chính xác cao hơn 4 lần, kết quả cho 0.1+0.1+0.1 bằng với giá trị biểu diễn 0.3 (xem ở trên, vẫn không bằng 0.3 về mặt tóan học).

Kết luận

Kiểu float64 trong Go vẫn tuân theo IEEE754, nên kết luận về kiểu float vẫn giống như Python. Điều khác biệt chỉ là một số trường hợp nhỏ xảy ra khi sử dụng const.

Go thật đơn giản, ha!

Hết.

HVN at http://pymi.vn and https://www.familug.org.

Ủng hộ tác giả 🍺

Friday, 27 January 2023

Go value receiver method vẫn thay đổi được giá trị?!

Bài viết đầu tiên của nhiều bài viết sẽ bán than về "Go" - ngôn ngữ lập trình non trẻ (since 2010) nhưng khá hot trên toàn cầu.

Go is simple

Go thường được nhìn nhận là "đơn giản", không phủ nhận việc làm xong gotour https://go.dev/tour/ khiến người học có thể bắt đầu viết code go, sau 1 tháng có thể tham gia code production (và viết bug).

Có hẳn cuốn sách giá cả triệu đồng bán rất chạy về những mistake khi dùng ngôn ngữ đơn giản này 100 Go Mistakes and How to Avoid Them.

Không dùng thì thôi, sao phải chê, thích chiến à?

Có một sự thật đi làm khá lâu mới nhận ra, rằng 99.99% BẠN không phải người quyết định công ty/dự án dùng cái gì. Những người/thứ quyết định sẽ là:

  • công nghệ có sẵn của công ty
  • công nghệ quen thuộc của team
  • bên trên chỉ xuống (CTO/architect...)
  • khách hàng yêu cầu

vậy nên thích hay không vẫn phải dùng. Mà dùng không sướng thì phê bình thôi. map bug

Go pointer receiver vs value receiver

Go receiver có 2 loại là pointer và value.

Trích từ Go tour https://go.dev/tour/methods/4

Methods with pointer receivers can modify the value to which the receiver points (as Scale does here). Since methods often need to modify their receiver, pointer receivers are more common than value receivers.

Choosing a value or pointer receiver

There are two reasons to use a pointer receiver. The first is so that the method can modify the value that its receiver points to. The second is to avoid copying the value on each method call. This can be more efficient if the receiver is a large struct, for example.

Theo hướng dẫn này, người học Go sẽ hiểu rằng khi viết

func (t *type) name(args)

thì có thể thay đổi t

func (t type) name(args)

thì không thể thay đổi t.

Ngoài ra, trong Go tour không còn chỗ nào ghi thêm gì nữa.

Thử ví dụ sau, đừng ôm đầu kêu đau:

package main

import (
    "fmt"
)

type toy struct {
    names []string
    ages  map[string]int
}

func (t *toy) change() {
    t.names = append(t.names, "python")
    t.ages["python"] = 32
}
func (t toy) mayNotChange() {
    t.names = append(t.names, "python")
    t.ages["python"] = 32
}

func main() {
    t := toy{ages: map[string]int{"golang": 12}}
    fmt.Printf("%v\n", t)
    t.change()
    fmt.Printf("%v\n", t)
    t2 := toy{ages: map[string]int{"golang": 12}}
    fmt.Printf("%v\n", t2)
    t2.mayNotChange()
    fmt.Printf("%v\n", t2)
}

chạy online trên https://go.dev/play/p/N781b6vT-GF

Theo tài liệu Go tour, change nhận 1 pointer receiver nên sẽ thay đổi toy t và kết quả không có gì bất ngờ khi map giờ chứa thêm python 32.

Nhưng trong mayNotChange, trong khi slice t.names không đổi thì map t.ages vẫn bị thay đổi dù cho đã dùng value receiver.

{[] map[golang:12]}
{[python] map[golang:12 python:32]}
====================================
{[] map[golang:12]}
{[] map[golang:12 python:32]}

Golang map là reference

Trong Effective Go có viết

Like slices, maps hold references to an underlying data structure. If you pass a map to a function that changes the contents of the map, the changes will be visible in the caller.

map refers tới cấu trúc dữ liệu bên dưới, thay đổi map sẽ thay đổi cấu trúc dữ liệu đó, còn bản thân map là 1 reference, không thay đổi, đúng như value receiver cam kết.

Bài tập cho bạn đọc: slice cũng là reference, sao map thay đổi còn slice thì không khi dùng value receiver? (đáp án xem cuối bài)

Thêm 1 ít tài liệu về 3 kiểu references: slice, map, channel https://go.dev/doc/effective_go#allocation_make

Back to allocation. The built-in function make(T, args) serves a purpose different from new(T). It creates slices, maps, and channels only, and it returns an initialized (not zeroed) value of type T (not *T). The reason for the distinction is that these three types represent, under the covers, references to data structures that must be initialized before use.

Kết luận

Golang map là kiểu reference, có thể thay đổi giá trị nó refer tới bên trong 1 value receiver method.

Go thật đơn giản, ha!

Hết.

HVN at http://pymi.vn and https://www.familug.org.

Ủng hộ tác giả 🍺

Đáp án: t.names = append(t.names, "python") sẽ gán giá trị mới cho t.names, do dùng value receiver nên không thấy thay đổi gì. Nếu thay đổi phần tử của slice, ví dụ t.names[0] = "Python" thì kết quả có thay đổi. Tương tự, nếu viết t.ages = map[string]int{} sẽ thấy map ages không thay đổi. Xem code tại https://go.dev/play/p/yCn-mKVpPSo

Friday, 20 January 2023

Tạo giao diện gdb như peda/gef/pwndbg

GDB là một debugger lâu đời có rất nhiều tính năng. Nó giống vim /emacs nhiều điểm:

  • nhiều tính năng nhưng mặc định thì không bật gì, khó dùng gdb_tui
  • cài thêm các tính năng vào sẽ ngon lành
  • việc cấu hình cũng cần phải học, nên có nhiều bản "distro" dùng sẵn.
  • sau hàng chục năm config chán chê, người dùng hardcore lại quay về 1 file config đơn giản.

GDB các bản mở rộng dễ dùng/tiện dụng hơn có:

Như vim có

Tương tự với emacs:

PS: tham khảo 2 phần trước:

PPS: nên đọc trên máy tính.

Tạo giao diện giống pwndbg/gef/peda

pwndbg

3 bản mở rộng này nhìn chung đều có giao diện giống nhau. Màn hình sẽ hiện "context" với 3 phần:

  • các register
  • disassembly code
  • stack

gdb lấy thông tin các register

Lệnh gdb info registers sẽ hiện thông tin của tất cả các register.

$ gdb ./a.out -q
Reading symbols from ./a.out...
(No debugging symbols found in ./a.out)
(gdb) start
Temporary breakpoint 1 at 0x1149
Starting program: /home/hvn/me/news/a.out

Temporary breakpoint 1, 0x0000555555555149 in main ()
(gdb) info registers
rax            0x555555555149      93824992235849
rbx            0x5555555551a0      93824992235936
rcx            0x5555555551a0      93824992235936
rdx            0x7fffffffe548      140737488348488
rsi            0x7fffffffe538      140737488348472
rdi            0x1                 1
rbp            0x0                 0x0
rsp            0x7fffffffe448      0x7fffffffe448
r8             0x0                 0
r9             0x7ffff7fe0d60      140737354009952
r10            0x7                 7
r11            0x2                 2
r12            0x555555555060      93824992235616
r13            0x7fffffffe530      140737488348464
r14            0x0                 0
r15            0x0                 0
rip            0x555555555149      0x555555555149 <main>
eflags         0x246               [ PF ZF IF ]
cs             0x33                51
ss             0x2b                43
ds             0x0                 0
es             0x0                 0
fs             0x0                 0
gs             0x0                 0
(gdb)

Để lấy thông tin của 1 register cụ thể, ví dụ rsp, gõ info register rsp.

So với pwndbg

 RAX  0x555555555149 (main) ◂— endbr64
 RBX  0x5555555551a0 (__libc_csu_init) ◂— endbr64
 RCX  0x5555555551a0 (__libc_csu_init) ◂— endbr64
 RDX  0x7fffffffe488 —▸ 0x7fffffffe80a ◂— 'SSH_AUTH_SOCK=/run/user/1000/keyring/ssh'
 RDI  0x1
 RSI  0x7fffffffe478 —▸ 0x7fffffffe7f2 ◂— '/home/hvn/me/news/a.out'
 R8   0x0
 R9   0x7ffff7fe0d60 (_dl_fini) ◂— endbr64
 R10  0x7
 R11  0x2
 R12  0x555555555060 (_start) ◂— endbr64
 R13  0x7fffffffe470 ◂— 0x1
 R14  0x0
 R15  0x0
 RBP  0x0
 RSP  0x7fffffffe388 —▸ 0x7ffff7dd8083 (__libc_start_main+243) ◂— mov    edi, eax
 RIP  0x555555555149 (main) ◂— endbr64

Thứ tự có thay đổi 1 chút và chỉ hiển thị tới RIP.

so với gef:

$rax   : 0x0000555555555149  →  <main+0> endbr64
$rbx   : 0x00005555555551a0  →  <__libc_csu_init+0> endbr64
$rcx   : 0x00005555555551a0  →  <__libc_csu_init+0> endbr64
$rdx   : 0x00007fffffffe488  →  0x00007fffffffe80a  →  "SSH_AUTH_SOCK=/run/user/1000/keyring/ssh"
$rsp   : 0x00007fffffffe388  →  0x00007ffff7dd8083  →  <__libc_start_main+243> mov edi, eax
$rbp   : 0x0
$rsi   : 0x00007fffffffe478  →  0x00007fffffffe7f2  →  "/home/hvn/me/news/a.out"
$rdi   : 0x1
$rip   : 0x0000555555555149  →  <main+0> endbr64
$r8    : 0x0
$r9    : 0x00007ffff7fe0d60  →  <_dl_fini+0> endbr64
$r10   : 0x7
$r11   : 0x2
$r12   : 0x0000555555555060  →  <_start+0> endbr64
$r13   : 0x00007fffffffe470  →  0x0000000000000001
$r14   : 0x0
$r15   : 0x0
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x33 $ss: 0x2b $ds: 0x00 $es: 0x00 $fs: 0x00 $gs: 0x00

Hiện đủ các registers.

Cả 2 phiên bản này đều có giá trị thứ 2 là tên function nếu có thể tìm được.

Có thể sử dụng lệnh x để thu được kết quả tương tự:

(gdb) x $rax
0x555555555149 <main>:  0xfa1e0ff3
(gdb) x $rbx
0x5555555551a0 <__libc_csu_init>:   0xfa1e0ff3

dùng x/i để "examine instruction" tại đó:

(gdb) x/i $rax
=> 0x555555555149 <main>:   endbr64

Viết câu lệnh context chạy info registers rồi lặp qua các register chạy x/i sẽ thu được kết quả như pwndbg/gef.

    def invoke(self, arg, from_tty):
        for line in self.run_command("info registers").splitlines():
            if not line.startswith("r"):
                continue
            register, _address, *contents = line.split()
            r = self.run_command(f"x/i ${register}").strip(" =>\n")
            if '<' in r and '>' in r:
                print("{:<15}{}".format(register, r))
            else:
                print(line)
(gdb) context
rax            0x555555555149 <main>:   endbr64
rbx            0x5555555551a0 <__libc_csu_init>:    endbr64
rcx            0x5555555551a0 <__libc_csu_init>:    endbr64
rdx            0x7fffffffe548      140737488348488
rsi            0x7fffffffe538      140737488348472
rdi            0x1                 1
rbp            0x0                 0x0
rsp            0x7fffffffe448      0x7fffffffe448
r8             0x0                 0
r9             0x7ffff7fe0d60 <_dl_fini>:   endbr64
r10            0x7                 7
r11            0x2                 2
r12            0x555555555060 <_start>: endbr64
r13            0x7fffffffe530      140737488348464
r14            0x0                 0
r15            0x0                 0
rip            0x555555555149 <main>:   endbr64

gdb disassemble code

Hiển thị các instruction tương ứng trước và sau vị trí "hiện tại". Trên kiến trúc x86, đó là register rip (register instruction pointer), trên kiến trúc khác có thể là register khác, vậy nên GDB dùng tên chung pc (program counter) để thay tương ứng cho từng kiến trúc.

Phần này có tên là DISASM hoặc CODE

   0x555555555149 <main>       endbr64
   0x55555555514d <main+4>     push   rbp
   0x55555555514e <main+5>     mov    rbp, rsp
   0x555555555151 <main+8>     sub    rsp, 0x20
   0x555555555155 <main+12>    mov    dword ptr [rbp - 0x14], edi
 ► 0x555555555158 <main+15>    mov    qword ptr [rbp - 0x20], rsi
   0x55555555515c <main+19>    mov    dword ptr [rbp - 0xc], 5
   0x555555555163 <main+26>    mov    dword ptr [rbp - 8], 7
   0x55555555516a <main+33>    mov    edx, dword ptr [rbp - 0xc]
   0x55555555516d <main+36>    mov    eax, dword ptr [rbp - 8]
   0x555555555170 <main+39>    add    eax, edx

Ta có thể lấy vị trí của $pc rồi in ra vài instruction trước và sau $pc, sử dụng lệnh x/10i $pc.

(gdb) set disassembly-flavor intel
(gdb) start
Temporary breakpoint 1 at 0x1161
Starting program: /home/hvn/me/news/main

Temporary breakpoint 1, 0x0000555555555161 in main ()
(gdb) x/10i $pc
=> 0x555555555161 <main>:   endbr64
   0x555555555165 <main+4>: push   rbp
   0x555555555166 <main+5>: mov    rbp,rsp
   0x555555555169 <main+8>: sub    rsp,0x20
   0x55555555516d <main+12>:    mov    DWORD PTR [rbp-0x14],edi
   0x555555555170 <main+15>:    mov    QWORD PTR [rbp-0x20],rsi
   0x555555555174 <main+19>:    mov    DWORD PTR [rbp-0xc],0x5
   0x55555555517b <main+26>:    mov    DWORD PTR [rbp-0x8],0x7
   0x555555555182 <main+33>:    mov    edx,DWORD PTR [rbp-0x8]
   0x555555555185 <main+36>:    mov    eax,DWORD PTR [rbp-0xc]

Khi binary không chứa debug_info (compile -g) như khi dev, không có sourcecode để gdb xem next - dòng code tiếp theo ứng với địa chỉ nào, ta phải tự đặt breakpoint tại địa chỉ rồi continue chạy tiếp:

(gdb) b *0x555555555182
Breakpoint 2 at 0x555555555182
(gdb) c
Continuing.

Breakpoint 2, 0x0000555555555182 in main ()
(gdb) x/10i $pc
=> 0x555555555182 <main+33>:    mov    edx,DWORD PTR [rbp-0x8]
   0x555555555185 <main+36>:    mov    eax,DWORD PTR [rbp-0xc]
   0x555555555188 <main+39>:    mov    esi,edx
   0x55555555518a <main+41>:    mov    edi,eax
   0x55555555518c <main+43>:    call   0x555555555149 <sum>
   0x555555555191 <main+48>:    mov    DWORD PTR [rbp-0x4],eax
   0x555555555194 <main+51>:    mov    eax,DWORD PTR [rbp-0x4]
   0x555555555197 <main+54>:    mov    esi,eax
   0x555555555199 <main+56>:    lea    rdi,[rip+0xe64]        # 0x555555556004
   0x5555555551a0 <main+63>:    mov    eax,0x0
(gdb)

Để in ra trước $pc, cần thực hiện 1 chút tính toán để tìm kiếm địa chỉ hợp lệ rồi in ra từ địa chỉ đó thay vì $pc, ví dụ ở đây sau khi tính được ra địa chỉ là $pc-21, ta có:

(gdb) x/10i ($pc-21)
   0x55555555516d <main+12>:    mov    DWORD PTR [rbp-0x14],edi
   0x555555555170 <main+15>:    mov    QWORD PTR [rbp-0x20],rsi
   0x555555555174 <main+19>:    mov    DWORD PTR [rbp-0xc],0x5
   0x55555555517b <main+26>:    mov    DWORD PTR [rbp-0x8],0x7
=> 0x555555555182 <main+33>:    mov    edx,DWORD PTR [rbp-0x8]
   0x555555555185 <main+36>:    mov    eax,DWORD PTR [rbp-0xc]
   0x555555555188 <main+39>:    mov    esi,edx
   0x55555555518a <main+41>:    mov    edi,eax
   0x55555555518c <main+43>:    call   0x555555555149 <sum>
   0x555555555191 <main+48>:    mov    DWORD PTR [rbp-0x4],eax

Ở đây, để đơn giản sẽ tạm bỏ qua tính toán và chỉ in từ $pc trở đi:

Code python để in ra phần DISASM:

        print(self.run_command("x/10i $pc"))

gdb hiển thị stack

Hiển thị stack là hiển thị giá trị của các địa chỉ từ $rsp (register stack pointer) tới $rbp (register base pointer):

pwndbg:

00:0000│ rsp 0x7fffffffe358 —▸ 0x555555555191 (main+48) ◂— mov    dword ptr [rbp - 4], eax
01:0008│     0x7fffffffe360 —▸ 0x7fffffffe478 —▸ 0x7fffffffe7f5 ◂— '/home/hvn/me/news/main'
02:0010│     0x7fffffffe368 ◂— 0x155555060
03:0018│     0x7fffffffe370 ◂— 0x5ffffe470
04:0020│     0x7fffffffe378 ◂— 0x7
05:0028│ rbp 0x7fffffffe380 ◂— 0x0
06:0030│     0x7fffffffe388 —▸ 0x7ffff7dd8083 (__libc_start_main+243) ◂— mov    edi, eax
07:0038│     0x7fffffffe390 —▸ 0x7ffff7ffc620 (_rtld_global_ro) ◂— 0x50f4a00000000
(gdb) print ($rbp-$rsp)
$4 = 40

Cách nhau 40 bytes, chia cho 4 được 10 "word" (1 word = 4 bytes)

(gdb) x/10w $rsp
0x7fffffffe358: 0x55555191  0x00005555  0xffffe478  0x00007fff
0x7fffffffe368: 0x55555060  0x00000001  0xffffe470  0x00000005
0x7fffffffe378: 0x00000007  0x00000000

Code Python:

        word_to_print = int(self.run_command("print ($rbp -$rsp)/4").split()[-1])
        print(self.run_command(f"x/{word_to_print}w $rsp"))

Hiện context sau mỗi câu lệnh gdb

GDB có "hook" để tự chạy mỗi câu lệnh trước/sau một hành động. hook-stop sẽ chạy trước mỗi câu lẹnh.

Thêm code Python:

gdb.execute("""define hook-stop
context
end""")

Kết quả

(gdb) b *0x555555555182
Breakpoint 2 at 0x555555555182
(gdb) c
Continuing.
rax            0x555555555161 <main>:   endbr64
rbx            0x5555555551c0 <__libc_csu_init>:    endbr64
rcx            0x5555555551c0 <__libc_csu_init>:    endbr64
rdx            0x7fffffffe548      140737488348488
rsi            0x7fffffffe538      140737488348472
rdi            0x1                 1
rbp            0x7fffffffe440      0x7fffffffe440
rsp            0x7fffffffe420      0x7fffffffe420
r8             0x0                 0
r9             0x7ffff7fe0d60 <_dl_fini>:   endbr64
r10            0x7                 7
r11            0x2                 2
r12            0x555555555060 <_start>: endbr64
r13            0x7fffffffe530      140737488348464
r14            0x0                 0
r15            0x0                 0
rip            0x555555555182 <main+33>:    mov    -0x8(%rbp),%edx
====================DISASM====================
=> 0x555555555182 <main+33>:    mov    -0x8(%rbp),%edx
   0x555555555185 <main+36>:    mov    -0xc(%rbp),%eax
   0x555555555188 <main+39>:    mov    %edx,%esi
   0x55555555518a <main+41>:    mov    %eax,%edi
   0x55555555518c <main+43>:    callq  0x555555555149 <sum>
   0x555555555191 <main+48>:    mov    %eax,-0x4(%rbp)
   0x555555555194 <main+51>:    mov    -0x4(%rbp),%eax
   0x555555555197 <main+54>:    mov    %eax,%esi
   0x555555555199 <main+56>:    lea    0xe64(%rip),%rdi        # 0x555555556004
   0x5555555551a0 <main+63>:    mov    $0x0,%eax

====================STACK====================
0x7fffffffe420: 0xffffe538  0x00007fff  0x55555060  0x00000001
0x7fffffffe430: 0xffffe530  0x00000005  0x00000007  0x00000000

Tô màu mè, format đẹp lại là có một phiên bản gdb do chính tay bạn làm ra.

Code at https://github.com/hvnsweeting/hgdb/blob/v0.1/hgdb.py

Kết luận

Python được dùng phổ biến để mở rộng tính năng cho các phần mềm khác (blender, calibre, ...)

Với sự có mặt của Python (và Guile), gdb đã cho phép mình tự biến hóa thành những sản phẩm đầy tiện lợi sang xịn mịn nhờ cồng đồng.

Và bạn cũng có thể tự làm agdb bgdb ... hgdb hay ... zgdb cho chính mình.

Hết.

HVN at https://pymi.vn and https://www.familug.org.

Ủng hộ tác giả 🍺

Wednesday, 4 January 2023

ElasticSearch/Java format thời gian không giống Python

Hầu hết các ngôn ngữ lập trình đều có sẵn thư viện để format thời gian, vì dù cho mỗi người một nghề - người làm web, sysadmin, kẻ làm data, scientist nhưng đâu ai thoát khỏi được thời gian?

Cứ tưởng các ngôn ngữ lập trình format sẽ giống nhau, học 1 lần là dùng được mãi, ai ngờ!

time Photo by Aron Visuals on Unsplash

Linux date, C, C++

Lấy làm mốc chuẩn, chương trình date trên UNIX/Linux dùng Ymd HMS cho ngày tháng năm giờ phút giây

$ date +"%Y/%m/%d %H:%M:%S"
2023/01/04 20:16:48
$ date --version
date (GNU coreutils) 8.30
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Written by David MacKenzie.

Có vẻ như bắt nguồn từ function strftime trong time.h của C https://en.cppreference.com/w/c/chrono/strftime, dễ hiểu C++ và Python cũng kế thừa và phát huy từ đó. Xem ví dụ tại https://strftime.org/

>>> import datetime
>>> datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")
'2023/01/04 20:27:21'

Java format time

Java (và thế nên ElasticSearch - 1 chương trình viết bằng Java), sử dụng format khác. Ở đây sẽ dùng lein repl để chạy code Clojure, gọi thư viện Java cho tiện (cài lein: sudo apt install leiningen).

$ lein repl                                                                                                                                            [0]
nREPL server started on port 32771 on host 127.0.0.1 - nrepl://127.0.0.1:32771
REPL-y 0.4.3, nREPL
Clojure 1.10.1
OpenJDK 64-Bit Server VM 11.0.17+8-post-Ubuntu-1ubuntu220.04
...
user=> (.format (java.text.SimpleDateFormat. "yyyy/MM/dd HH:mm:ss") (new java.util.Date))
"2023/01/04 20:33:12"
  • Java dùng y thường cho năm, M hoa cho tháng , m thường cho phút, s thường cho giây.
  • Python dùng Y hoa cho năm, m thường cho tháng, M hoa cho phút, S hoa cho giây.

Go lang - một mình một kiểu

Ra đời tận những năm 2000, Go quyết định format 1 mình 1 kiểu không giống ai trên đời:

package main

import (
    "fmt"
    "time"
)

func main() {
    t := time.Now()
    fmt.Printf("%s\n", t.Format("2006/01/02 15:04:05"))
}

https://pkg.go.dev/time#pkg-constants

Kết luận

Vì đúng sai chỉ là do góc nhìn, nên các công nhân phải chịu làm dâu trăm họ để có tiền mua cơm.

Hết.

HVN at https://pymi.vn and https://www.familug.org.

Ủng hộ tác giả 🍺

Monday, 2 January 2023

Xiaomi - điện thoại thân thiện quảng cáo, mua ngay, giá hot

Android là hệ điều hành "mã nguồn mở", với dân công nghệ từ khóa này rất hấp dẫn, nó có nghĩa là "cái gì không thích thì mình có thể thay đổi" (code), sự thật thì ... rất rất xa.

Android mã nguồn mở - mở ra chả làm được gì

Đúng là Android là mã nguồn mở https://source.android.com, nhưng để tự build 1 cái ROM cài và dùng được cho máy điện thoại của bạn là một điều không hề đơn giản.

Riêng yêu cầu 400GB ổ cứng đã gần như giới hạn hầu hết developer thường từ bỏ chuyện này. Thuê server, lấy ví dụ DigitalOcean, 32GB RAM 400GB ổ cứng SSD mất $376/tháng, đủ 1 một con điện thoại tầm trung.

https://www.digitalocean.com/pricing/droplets

32 GiB  16 vCPUs    7,000 GiB   2x  400 GiB $0.55952    $376.00

nếu toàn bộ quá trình từ cài đặt/thử sai mất 5 ngày thì tốn ~$20 ~ 500.000 VND.

Giải pháp còn lại chỉ có

  • lên các trang tin cậy tải về https://wiki.lineageos.org/devices/ - nhưng lượng thiết bị được hỗ trợ cũng rất hữu hạn, tới 2023/1/1 chỉ có 1 thiết bị sản xuất năm 2022 được support, hầu hết các điện thoại của SamSung 3 năm trở lại đây đều không được hỗ trợ. https://johannes.truschnigg.info/tmp/lineageos_device_toplist.txt. PS: trang https://lineageosroms.com/ không hề liên quan đến LineageOS, ROM ở đây không phải chính thức.
  • Lên https://forum.xda-developers.com/ tải ROM được người dùng khác build sẵn về - việc này về mặt bảo mật khá nhạy cảm/nguy hiểm, bạn không thể biết người dùng đó có nhét sẵn gì trong bản ROM không.

Và cuối cùng, việc build Android ROM chỉ còn là cuộc chơi của những nhà sản xuất điện thoại: SamSung, XiaoMi, ... ở Việt Nam liệu còn nhãn hiệu nào khác???!!!

Điện thoại Xiaomi

Nếu mua 1 chiếc điện thoại, bạn muốn có... 1 chiếc điện thoại để dùng, thì ngày nay, mua điện thoại hay Tivi Android, bạn bị bắt buộc phải dùng các phần mềm để xem quảng cáo. Trên tay máy Redmi 9T, cài sẵn "MIUI 13.0.2", trải nghiệm thực sự khủng khiếp.

  • Công bố rõ ràng ngay khi cài đặt máy lần đầu là không xem ads "tối ưu" thì xem ads chung chung. mi dirt
  • Cài sẵn phần mềm Wallpaper/Theme/Ringtone khi bấm vào hiện quảng cáo. Bạn muốn set 1 bức ảnh làm hình nền? app sẽ được mở ra trước để hiện quảng cáo, bán theme. mi ads
  • Bạn muốn đổi nhạc chuông? App sẽ mở ra và hiện quảng cáo, phải đóng quảng cáo rồi mới hiện giao diện, để chọn file nhạc sẵn trên máy phải bấm vào nút bé tí. mi ads again
  • Khi khóa màn hình, góc phải để bật camera, góc trái là để mở app đa năng "Wallpaper Carousel" nói trên, kèm hiện "tin tức". May mắn thay có thể tắt app WC này đi để có thể đổi Wallpaper mà không hiện quảng cáo, nhưng nó vẫn nằm ở góc màn hình lock và đổi nhạc chuông vẫn vậy. mi dark pattern
  • Thi thoảng app sẽ gửi notification mời dùng theme/wallpaper mới mi noti
  • Xem file cũng hiện quảng cáo - đóng góp bởi bạn đọc Phú Khang - người dùng Xiaomi 5 năm không biết có quảng cáo mi file
  • App cài sẵn hàng loạt app "hữu dụng" của Xiaomi, như "MiCoin", không có nút uninstall. mi crapware mi coin

Điện thoại SamSung

Điện thoại SamSung năm 2020 cũng cài sẵn 1 đống crapware, nhưng dù sao không có quảng cáo.

https://www.familug.org/2020/01/samsung-bloat.html

Kết luận

Không bao giờ mua thêm một cái điện thoại Xiaomi nào nữa.

Người dùng tin tưởng Apple có thể dùng IPhone, dù sao thì cũng không có quảng cáo. Còn người dùng Android... câu chuyện vẫn còn nan giải vì chẳng tin ai.

Hết.

HVN at https://pymi.vn and https://www.familug.org.

Ủng hộ tác giả 🍺