Skip to content

Blog

Fun Facts about the 8088

Note: A classic compilation of x86 low-level optimization tricks originally attributed to Microsoft engineer Chris Peters in the 1980s. Preserved here as a historical document on the craft of shaving off single bytes and clock cycles on the Intel 8088 prefetch queue and bus architecture.

1. Comparing a register

The fastest and smallest way to compare a 16 bit register to zero is to OR it with itself, e.g.

    OR      BX,BX           ; 2 bytes, 3 clocks
    JGE     BXisPositive

this is much better than comparing it with zero, e.g.

    CMP     BX,0    ; 3 bytes, 4 clocks (bush league)

For the ultimate in comparing with zero, try to use the CX register. The 8088 contains the single instruction:

    JCXZ    CXisZero        ; jump if CX is zero

This instruction makes a short jump if CX is zero.

To destructivly test for 1 or -1, use the DEC or INC instructions:

    DEC     DX              ; 1 byte, 3 clocks
    JZ      DXisOne         ; if zero, DX was 1
or
    INC     DX              ; 1 byte, 3 clocks
    JZ      DXisMinusOne    ; if zero, DX was -1

The LOOP instruction is just a fancy way of writing:

    DEC     CX
    JNZ     CXisNotZero

The difference is that LOOP is 1 byte smaller and 2 clocks faster. The LOOP instruction can be used to compare CX with multiple values as follows:

    LOOP    CXisNotOne       ; If CX = 1 then...
    ...                      ; ...do this code, else...

CXisNotOne:

    LOOP    CXisNotTwo       ; if CX = 2 then...
    ...                      ; ...do this code, else... 

CXisNotTwo:

    LOOP    CXisNotThree     ; if CX = 3 then...
    ...                      ; ...do this code, else etc.

Its possible to check if a signed number is in the range 0-n with a single comparison to n:

    CMP     DX,639          ; if <0 or >639...
    JA      OutOfRange      ; ...its out of range

This is smaller and faster than:

    OR      DX,DX           ; Never CMP DX,0!
    JL      OutOfRange      ; If negative, its out of range
    CMP     DX,639
    JG      OutOfRange      ; if greater, its out of range

This can be generalized to any signed compare within a specific range. If you wanted to make sure the AX register contained a number from -5 to 17:

    SUB     AX,-5           ; Subtract off lower bound
    CMP     AX,17-(-5)      ; Compare upper - lower bound
    JA      OutOfRange

You cannot compare a segment register. To do so copy it to a register or memory location, then compare it.

2. Setting a register to zero

To set a register to zero, the smallest, fastest way is to XOR it with itself, e.g.

        XOR     BX,BX           ; 2 bytes, 2 clocks

is smaller and faster than:

        MOV     DX,0            ; 3 bytes, 4 clocks

There is one side affect: MOVing does not affect the flags, but XORing does. The 8088 aficianado will only move a zero into a register in the rare cases where the flags must be preserved.

3. Incrementing, Decrementing

It is smaller to increment or decrement a 16 bit register then an 8 bit register, so if it doesnt matter, use a 16 bit register, e.g.:

        INC     DX              ; 1 byte, 3 clocks

is smaller than:

        INC     DL              ; 2 bytes, 3 clocks

Same thing goes for decrementing, its smaller to use a 16 bit register.

Its smaller (but not faster) to increment a register twice then to add 2 to it, e.g.

    INC     DX              ; 1 byte,  3 clocks (total)
    INC     DX              ; 2 bytes, 6 clocks 

is smaller and slower than:

    ADD     DX,2            ; 3 bytes, 4 clocks

One side affect: INC and DEC do not affect the carry flag, ADD does.

4. If Then Else

When confronted with an If, Then, Else problem the assembly language programmer will often write it as Else, If, Then. For example, a sample problem might be to return 123H in the DX register if AL<5, otherwise return zero in DX. Using If, Then, Else produces:

     CMP     AL,5                ; Is AL less than 5?
     JB      ALisBelow5          ; yes...
     XOR     DX,DX               ; Set DX to zero if AL>=5
     JMP     SHORT Continue      ; proceed 
ALisBelow5:
     MOV     DX,123H             ; Set DX to 80H if AL<5
Continue:

Using Else, If, Then:

     MOV     DX,123H             ; Set DX to 80H if AL<5
     CMP     AL,5                ; Is AL less than 5?
     JB      ALisBelow5          ; yes...
     XOR     DX,DX               ; Set DX to zero if AL>=5
ALisBelow5:

The idea is to do the work for the most likely case, then do the comparision. If you were right you're all done, if not then do the other case.

5. Copying strings

One of the simplest ways to copy a null terminated string is as follows:

;
;  DS:SI points at source
;  ES:DI points at destination
;
CopyString:
    LODSB                ; read character into AL, inc SI
    STOSB                ; store charcter, increment DI
    OR      AL,AL        ; was it the null
    JNZ     CopyString   ; no, repeat

A fast way to copy a string where the count is known is

    SHR     CX,1    ; divide count by two (2 bytes = 1 word)
    REP     MOVSW   ; move the first part fast
    JNC     Even    ; No carry if count was even
    MOVSB           ; move the odd byte
Even:

Slightly faster but slightly bigger is:

    SHR     CX,1    ; Get the word count
    REP     MOVSW   ; move the words, leaving CX = 0
    RCL     CX,1    ; if count was odd make CX = 1 
    REP     MOVSB   ; (possibly) move last byte

This is slightly faster because there is no jump to empty the prefetch queue.

The REP instruction checks to see if the loop count in CX is zero before starting, there is no need to check it beforehand.

6. Testing long pointer for null

Sometimes its necessary to check if a long pointer is null before using it. A sequence of code that works well is:

     LES     DI,[LongPointer]        ; get the long pointer
     MOV     CX,ES                   ; copy ES to CX
     JCXZ    PointerIsNull           ; if zero, dont use it

This assumes segment zero is an invalid pointer.

7. Exchanging

To move a register to or from AX takes 2 bytes and 2 clocks

    MOV     AX,DX           ; 2 bytes, 2 clocks

However, to exchange a register with the AX register takes 1 byte and 3 clocks:

    XCHG    AX,DX           ; 1 byte, 3 clocks

A clear case where its possible to optimize for size or speed. This optimization is only available with the AX register. A full understanding is actually more complex. Although XCHG takes 3 clocks it is often faster because it only uses one byte of the prefetch queue.

8. Testing bits

The 8088 contains an instruction that allows you to test various bits. It works by doing a non destructive AND operation.

    TEST    AX,0800H        ; 4 bytes, 5 clocks
    JZ      BitIsOff

If one of the bytes is zero (a common occurence) this can be optimized to:

    TEST    AH,08H          ; 3 bytes, 4 clocks
    JZ      BitIsOff

The best way to test the hi bit is:

    OR      AX,AX           ; 2 bytes, 2 clocks
    JNS     HiBitIsOff      ; jump not signed (hi bit zero)

A destructive way to test the low order bit is to shift it to the right into the carry flag:

    SHR     AX,1            ; 2 bytes, 2 clocks
    JNC     LoBitIsOff

You can test multiple bits with the TEST instruction:

    TEST    BL,11000000b    ; check 2 highest bits
    JZ      BothAreZero     ; if zero, both are zero

Sometimes you want to jump if either are zero instead of both zero, this can usually be accomplished by the NOT instruction and reversing the sense of the jump instruction:

    NOT     BL              ; reverse the bits   
    TEST    BL,11000000b    ; check 2 highest bits
    JNZ     EitherAreZero   ; if either were zero, then this 
                            ; is non zero

9. Absolute value

A fascinating way to get the absolute value of the AX register was discovered by Marlin Eller:

    CWD             ; replicate hi order bit of AX into DX
    XOR     AX,DX   ; do a 1's complement or do nothing
    SUB     AX,DX   ; add 1 to get a 2's complement

The boring method does not affect the DX register and can be used on any register:

    OR      BX,BX   ; never CMP BX,0!
    JGE     NotNeg  ; if negative...
    NEG     BX      ; ...make it positive
NotNeg:

The boring method empties the prefetch queue with the JGE instruction, making it much slower.

10. Length of null terminated string

To get the length of a null terminated string, scan for the null at the end with a starting count of -1

;
;  ES:DI points at null terminated string
;
    XOR     AL,AL         ; look for null   2 bytes (total)
    MOV     CX,-1         ; CX = -1         5 bytes
    REPNE   SCASB         ; CX = -len-2     7 bytes
    NOT     CX            ; CX =  len+1     9 bytes
    DEC     CX            ; CX =  len      10 bytes

This count does not include the null at the end. If you want it to include the null, just delete the final DEC CX. The use of the NOT instruction is quite interesting here.

11. Returning flags

The 8088 contains instructions for setting (STC) and clearing (CLC) the carry flag. To set the zero flag, simply compare some register with itself:

    CMP     DX,DX            ; set zero flag

To clear the zero flag, OR the stack pointer with itself:

    OR      SP,SP           ; clear zero flag

This is making the safe assumption that the stack pointer is not zero.

12. Shifting

Variable count shifting is slow on the 8088. Its faster to shift twice then to set a count of 2:

    SHR     AX,1    ; 2 bytes, 2 clocks  (total)
    SHR     AX,1    ; 4 bytes, 4 clocks

is much faster than:

    MOV     CL,2    ; 2 bytes,  4 clocks  (total)
    SHR     AX,CL   ; 4 bytes, 20 clocks!

Variable count shifting is slower when shifting less than 5 bits, after that the prefetch queue makes variable shift counts faster.

13. Multiply and Divide

The multiply and divide instruction are some of the slowest instructions on the 8088. To give some perspective, a register to register MOV instruction takes 2 clocks, while a signed divide (IDIV) using registers can take 184 clocks.

If your goal is to write fast 8088 code, multiplying by constants can usually be done as a series of shifts and adds:

    ;
    ;  Multiply the AX register by 10
    ;
    SHL     AX,1            ; AX = AX *  2    ( 2 clocks)
    MOV     BX,AX           ; BX = AX *  2    ( 4 clocks)
    SHL     AX,1            ; AX = AX *  4    ( 6 clocks)
    SHL     AX,1            ; AX = AX *  8    ( 8 clocks)
    ADD     AX,BX           ; AX = AX * 10    (11 clocks)

A multiply would be more than 10 times slower, but would take fewer bytes. Multiply is useful when neither argument is constant or you need to save bytes.

Mark Zbikowski uses this method to divide the AX register by 512:

    SHR     AX,1         ; divide AX by 2
    XCHG    AL,AH        ; divide AX by 512 (AX is unsigned)
    CBW                  ; AL < 128, so this sets AH to 0

14. Converting bytes to segments

To convert a byte count to a paragraph count try:

    ;
    ;  DX contains a byte count
    ;
    ADD     DX,15       ; round up to next paragraph
    MOV     CL,4        ; 2^4 = 16 bytes per paragraph
    SHR     DX,CL       ; divide by 16 by shifting 4 times

DX now contains a paragraph count. This assumes the value in DX is less than 0FFF1H. To cover the extended case:

    ADD  DX,15        ; round up to next paragraph
    RCR  DX,1         ; divide by 2, including carry 
    MOV  CL,3         ; 2^3 = 8
    SHR  DX,CL        ; divide by a total of 16

15. Call, Return, Jump

A near call followed by a near return can always be replaced with a near jump:

    JMP NearProc  ; 3 bytes, 15 clocks

is smaller and much faster than:

    CALL  NearProc  ; 3 bytes, 19 clocks (total)
    RET             ; 4 bytes, 35 clocks 

Its often possible to eliminate the JMP entirely by moving the subroutines adjacent to each other.

Conditional jumps on the 8088 are always short, i.e. the destination must be within -128 to 127 of the instruction pointer. It seems every time a single line of new code is added some conditional jump becomes out of range. One technique to get around this is to find a similiar conditional jump to jump to:

    JC      OutOfRange     ; I want to jump to disk error...
    ...                    ; ...but its too far away, so... 

OutOfRange:

    JC      DiskError      ; I jump to this test for carry

Although this is not in the scope of this document, out of range jumps are usually the 8088 telling you that your subroutines have grown too large and should be broken up.

16. Multiple Entry Points

An old 8080 trick involving multiple entry points can be adapted to the 8088. Instead of doing this:

    Entry1:
        MOV     AL,1                    ; 2 bytes (total)
        JMP     SHORT EntryCommon       ; 4 bytes
    Entry2: 
        MOV     AL,2                    ; 6 bytes
        JMP     SHORT EntryCommon       ; 8 bytes
    Entry3:
        MOV     AL,3                    ;10 bytes 
    EntryCommon:

The hearty and brave will do this:

    Entry1:
        MOV     AL,1                    ; 2 bytes (total)
        DB      03DH                    ; 3 bytes 
    Entry2:
        MOV     AL,2                    ; 5 bytes
        DB      03DH                    ; 6 bytes
    Entry3:
        MOV     AL,3                    ; 8 bytes
    EntryCommon:                        ; flags are modified

The DB 03DH is the opcode for a CMP AX,xx. In this case the bogus CMP AX's are used to swallow up the MOV AL,x that follow.

A special case of this discovered by Pat Tharp optimizes for dual entry points:

    TrueEntry:        ; come here to set AX = TRUE
       DB  0B8H        ; (opcode for MOV AX,...)
    FalseEntry:       ; come here to set AX = FALSE
       XOR     AX,AX   ; AX = 0 = FALSE

Whats happening here is that the 0B8H is the opcode for MOV AX,### which will put the opcode for the XOR AX,AX (nonzero C031H) in AX. This is a clear win of 3 bytes over the boring method:

    TrueEntry:
      MOV     AL,1    ; set AX nonzero
      JMP     SHORT EntryCommon
    FalseEntry:
      XOR     AX,AX   ; set AX zero
    EntryCommon:

17. Assertion Macros

When using these advanced techniques its important not to expose yourself to bugs caused by changing constants in your program. For instance, in the Multiply and Divide section of this document there is a code to quickly multiply by ten. If the constant should later change from ten to twelve this code would no longer work. An assertion macro would flag this code as being in error, saving many hours of needless debugging. In cannot be stressed to strongly that advanced 8088 programming requires liberal use of assertion macros and extra documentation.

Dynamic Markdown

Markdown è un sistema di markup che può essere utilizzato per aggiungere elementi di formattazione a dei normali file di testo. È molto apprezzato dagli sviluppatori, e non a caso è il sistema di riferimento per GitHub oppure per StackOverflow ma può essere utilizzato per qualsiasi tipo di documento, compresi report, libri, manuali, testi, etc.

Uno strumento indispensabile per lavorare, tra le altre cose, su documenti markdown è pandoc che viene definito come il coltellino svizzero per i documenti di testo. Può essere infatti utilizzato per convertire documenti markdown in documenti Word, Open Office, LaTeX, PDF, etc.

Ad esempio:

pandoc test.md -s -o test.odt
oppure:

pandoc test.md -s -o test.pdf

(necessario aver installato pdflatex, come ad es. portable MikTex, e configurato il PATH in maniera adeguata).

È possibile inoltre applicare dei template di alta qualità, come il template Eisvogel

pandoc test.md -s -o test.pdf --template eisvogel

Markdown Dinamico

Ho pensato che sarebbe estremamente comodo poter disporre di un documento markdown "dinamico". Ad esempio, per inserire delle tabelle in un documento Markdown posso utilizzare il plugin MarkDown per Adminer che ho scritto qualche tempo fa, in modo molto semplice e con i risultati spesso migliori di un copia-incolla da Excel su documento Word!

Tuttavia sarebbe meglio ancora poter pescare i dati direttamente dal database di origine (così come da un CSV, XLSX, o quant'altro).

Un sistema che potrebbe funzionare è Jinga2 ovvero uno dei più utilizzati framework per template di Python. Questo sistema però, per precisa scelta architetturale, non prevede la possibilità di mescolare codice di programmazione con condice markup. Avendo vissuto in prima persona gli anni '90 dello scorso millennio, dove il codice HTML si mischiava al codice di programmazione senza capire bene il limiti di dove finiva uno ed iniziava l'altro, posso dire che tale scelta è perfettamente condivisibile. Tuttavia, pur essendo una opzione sempre valida, non è quello che stavo cercando!

Linguaggi di scripting?

Si potrebbe usare un Makefile con qualche script bash, magari integrato con dei sistemi che generano output in formato markdown come il mio perl-Sql-Textify.

Oppure PHP che, nel bene e nel male, permette di inserire del codice all'interno di un documento e può quindi essere utilizzato per rendere un documento Markdown dinamico.

Oppure ancora il buon vecchio HTML::Mason, che mi ha dato grandi soddisfazioni all'inizio del millennio permettendo di integrare codice HTML con il linguaggio perl. Purtoppo né HTML::Mason né Mason2 sono attivamente sviluppati da anni, anche se proprio nel momento in cui ho iniziato a scrivere questo post (ovvero il giorno 11 febbraio 2023 - scrivo molto lentamente!) è stato pubblicato HTML::Mason versione 1.60 che contiene un piccolo bugfix. Non ci sono poi aggiornamenti successivi.

Script Python?

Ho pensato di risolvere il problema con dei piccoli script Python. Un documento Markdown sarà composto come segue, con del codice Python inserito all'interno del documento:

---
title: "Report Dinamico"
author: [Federico Thiella]
date: 2023-09-11
subject: "Report Dinamico"
keywords: [codice, python, esempio]
lang: "it"
table-use-row-colors: True
book: True
...
^ import mktools as mk
^ c={'host':'myhost.mshome.net', 'database':'mydatabase', 'user':'myuser', 'password':'mypassword'}
# Report Dinamico creato con Markdown

^ q=mk.query_db("select * from sezioni", c)
^ for r in q['rows']:

## Titolo: <& r[2] &>

<& r[3] &>

Inserisci una tabella:

^   w=mk.query_db("""
^      select
^        col1,
^        col2,
^        col3
^      from
^        progetti
^      where
^        progetto_id={}
^      order by
^        col1, col2, col3
^      """.format(str(r[0])), c)
^   w['head'][1]['name']='Colonna 1'
^   w['head'][1]['align']='right'
^   w['head'][2]['name']='Colonna 2'
^   w['head'][2]['align']='right'
^   w['head'][3]['name']='Colonna 3'
^   w['head'][3]['align']='left'
^   mk.markdown_table(w['head'], w['rows'])
^^
\pagebreak
  1. questo codice Markdown dinamico verra compilato dallo script pp.py in uno script puro python, che può essere eseguito e che genererà in output il codice Markdown statico finale, pronto da poter essere utilizzato e/o convertito in altri formati;
  2. la libreria mktools.py contiene delle funzioni utili per eseguire velocemente delle operazioni su database:
  3. mk.query_db esegue una query e restituisce intestazione w['head'] e righe w['rows'];
  4. mk.markdown_table che converte la tabella descritta da intestazione e righe in formato markdown.

La compilazione del documento avviene in questo modo:

python pp.py -i documento.md | python | pandoc -o documento.pdf --template eisvogel.latex -s
(lo script dinamico e la libreria di utilities saranno pubblicate e descritte su GitHub quanto prima. Non si tratta di un sistema "professionale" ).

Mako Templates

Un'ultima opzione può essere il sistema Mako Templates, la cui filosofia "Don't reinvent the wheel...your templates can handle it!" contrasta con il mio paragrafo precedente. Lo approfondirò appena mi sara possibile!

Numeri di telefono riciclati: una falla di sicurezza?

Commento del febbraio 2023 su un blog, in risposta a un articolo che raccontava il caso di un utente che aveva ereditato per caso l'account WhatsApp di un'altra persona a causa del riutilizzo del numero di telefono.

Quando mi è stato assegnato il primo cellulare di lavoro, molti anni fa, ho cominciato a ricevere SMS e telefonate da amici che non sapevo di avere, che mi invitavano a feste ed eventi vari.

Una signora, che si spacciava per mia nonna, mi chiamava spesso piangendo per dirmi che, anche se mi rifiutavo di riconoscerla, per lo meno avevo ripreso a risponderle al telefono.

Infine un ragazzo mi accusava di avergli rubato cellulare e numero di telefono.

Gli operatori telefonici riutilizzano sempre i numeri di telefono, nel mio caso devono averlo fatto un po' troppo in fretta.

Il problema

Per quanto riguarda questa falla di sicurezza, mi sembra talmente scontata che mi stupisco che sia stata sottovalutata: nella maggior parte dei servizi di messaggistica il numero di telefono viene verificato solo in fase di attivazione (tramite chiamata o SMS) e poi l'applicazione viaggia su binari diversi rispetto alla SIM.

Puoi cambiare SIM, numero di telefono, puoi anche togliere la SIM, etc. ma a WhatsApp la cosa non interessa. Se un numero viene riassegnato, WhatsApp come fa a sapere se sei il vecchio utente di prima che ha cambiato telefono o un nuovo assegnatario del numero?

Le policy di cancellazione automatica per inattività (come i canonici 45 giorni previsti da alcune piattaforme) mitigano solo in parte il fenomeno, ma restano una toppa parziale.

Lo stesso problema con i domini

Ah, il problema succede anche con i domini internet. Se un dominio viene riassegnato, cosa che avviene per diverse ragioni non sempre volontarie, il nuovo proprietario può non solo leggere tutta la tua (nuova) corrispondenza, ma anche prendere accesso a tutti gli account esterni se registrati con una casella di posta legata al dominio "perduto".

Il pattern è sempre lo stesso: una risorsa viene riassegnata, ma i servizi che ci si appoggiano non lo sanno.

Windows Home Profile

Percorso Profilo Utente di Windows

La variabile %HOMEPROFILE% contiene il percorso del profilo corrente, tipicamente:

c:\Users\username

mentre la cartella dei documenti è di default la sua sottocartella Documents quindi:

c:\Users\username\Documents

tuttavia il nome della cartella Documents è configurabile e potrebbe essere stato impostato a piacere, per cui non si può dare per scontato che il percorso %HOMEPROFILE%\Documents sia sempre quello corretto.

Il percorso corretto è registrato nella seguente posizione del registro di Windows:

λ reg query "HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" /v Personal

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders
Personal    REG_SZ    C:\Users\username\Documents
Alcuni linguaggi di programmazione mettono a disposizione librerie e funzioni apposite per interrogare il registro, mentre con la shell è necessario utilizzare uno script apposito.

Script di lettura Percorso

Il seguente script legge il percorso richiesto, e lo memorizza nella variabile %value%:

    @echo OFF
    setlocal ENABLEEXTENSIONS

    set KEY_NAME="HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
    set VALUE_NAME="Personal"

    for /F "tokens=2*" %%A IN ('reg query %KEY_NAME% /v %VALUE_NAME%') do (
        set value=%%B
    )
    echo %value%

questo script sembra funzionare sia con le versioni più recenti di Windows che con le precedenti, e supporta percorsi con gli spazi.

Portable LaTex

Dato che ho l'esigenza di creare dei documenti LaTex sulla mia macchina Windows 11, ho verificato quali soluzioni siano possibili.

Esiste un'ampia scelta di possibilità ma dato che il mio utilizzo sarà saltuario, ho ritenuto che la versione MikTex fosse la più vicina alle mie esigenze, soprattutto per il fatto che si trova disponibile anche in versione Portable.

Ho quindi deciso di installarla sotto la mia cartella usuale per i software portable:

 c:\Users\[username]\Documents\sw\

Nella sezione MikTex Downloads si trova la versione Windows, ed è possibile scegliere l'installer standard, la versione "portable" oppure la versione installabile a riga di comando, utile per distribuire il software in automatico su numerosi computer aziendali.

La versione "portable" in realtà non richiede un installer separato: va semplicemente scaricato l'installer per windows e rinominato in miktex-portable.exe. Una volta eseguito è necessario assicurarsi di scegliere la seguente cartella di destinazione:

c:\Users\[username]\Documents\sw\miktex\

al cui interno si troverà lo script miktex-portable.cmd pronto per essere lanciato.

Tale script una volta lanciato apre una piccola icona nella barra di windows, dalla quale è possibile lanciare il terminale TeXworks. Oppure, in alternativa, è possibile eseguire ogni programma che si troverà nel seguente percorso:

c:\Users\[username]\Documents\sw\miktex\texmfs\install\miktex\bin\x64\

Un file di partenza che ho incominciato ad utilizzare è il seguente:

\documentclass{article}

\usepackage{graphicx}
\usepackage{amsmath}

\input{./pandoc_syntax.tex}

\begin{document}
    \input{./title.tex}
    \tableofcontents
    \newpage

    \section{Hello World!} % creates a section

    \textbf{Hello World!} Today I am learning \LaTeX. %notice how the command will end at the first non-alphabet charecter such as the . after \LaTeX
     \LaTeX{} is a great program for writing math. I can write in line math such as $a^2+b^2=c^2$ %$ tells LaTexX to compile as math
     . I can also give equations their own space: 
    \begin{equation} % Creates an equation environment and is compiled as math
    \gamma^2+\theta^2=\omega^2
    \end{equation}
    If I do not leave any blank lines \LaTeX{} will continue  this text without making it into a new paragraph.  Notice how there was no indentation in the text after equation (1).  
    Also notice how even though I hit enter after that sentence and here $\downarrow$
     \LaTeX{} formats the sentence without any break.  Also   look  how      it   doesn't     matter          how    many  spaces     I put     between       my    words.

    For a new paragraph I can leave a blank space in my code.
\end{document}

dove ho importato la seguente definizione dei colori:

% color syntax from pandoc
\usepackage{color}
\usepackage{fancyvrb}
\newcommand{\VerbBar}{|}
\newcommand{\VERB}{\Verb[commandchars=\\\{\}]}
\DefineVerbatimEnvironment{Highlighting}{Verbatim}{commandchars=\\\{\}}
\newenvironment{Shaded}{}{}
\newcommand{\AlertTok}[1]{\textcolor[rgb]{1.00,0.00,0.00}{\textbf{#1}}}
\newcommand{\AnnotationTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{#1}}}}
\newcommand{\AttributeTok}[1]{\textcolor[rgb]{0.49,0.56,0.16}{#1}}
\newcommand{\BaseNTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{#1}}
\newcommand{\BuiltInTok}[1]{#1}
\newcommand{\CharTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{#1}}
\newcommand{\CommentTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textit{#1}}}
\newcommand{\CommentVarTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{#1}}}}
\newcommand{\ConstantTok}[1]{\textcolor[rgb]{0.53,0.00,0.00}{#1}}
\newcommand{\ControlFlowTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{\textbf{#1}}}
\newcommand{\DataTypeTok}[1]{\textcolor[rgb]{0.56,0.13,0.00}{#1}}
\newcommand{\DecValTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{#1}}
\newcommand{\DocumentationTok}[1]{\textcolor[rgb]{0.73,0.13,0.13}{\textit{#1}}}
\newcommand{\ErrorTok}[1]{\textcolor[rgb]{1.00,0.00,0.00}{\textbf{#1}}}
\newcommand{\ExtensionTok}[1]{#1}
\newcommand{\FloatTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{#1}}
\newcommand{\FunctionTok}[1]{\textcolor[rgb]{0.02,0.16,0.49}{#1}}
\newcommand{\ImportTok}[1]{#1}
\newcommand{\InformationTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{#1}}}}
\newcommand{\KeywordTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{\textbf{#1}}}
\newcommand{\NormalTok}[1]{#1}
\newcommand{\OperatorTok}[1]{\textcolor[rgb]{0.40,0.40,0.40}{#1}}
\newcommand{\OtherTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{#1}}
\newcommand{\PreprocessorTok}[1]{\textcolor[rgb]{0.74,0.48,0.00}{#1}}
\newcommand{\RegionMarkerTok}[1]{#1}
\newcommand{\SpecialCharTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{#1}}
\newcommand{\SpecialStringTok}[1]{\textcolor[rgb]{0.73,0.40,0.53}{#1}}
\newcommand{\StringTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{#1}}
\newcommand{\VariableTok}[1]{\textcolor[rgb]{0.10,0.09,0.49}{#1}}
\newcommand{\VerbatimStringTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{#1}}
\newcommand{\WarningTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{#1}}}}
% end of color syntax

e la seguente prima pagina:

\begin{titlepage}
    \newcommand{\HRule}{\rule{\linewidth}{0.4mm}}

    \center
    \textsc{\LARGE Marjor heading}\\[0.5cm] % Major heading such as course name
    \textsc{\large Main heading}\\[0.5cm] % Main heading such as the name of your university/college
    \textsc{\large Course title}\\[1.5cm] % Minor heading such as course title
    \includegraphics[width=4cm]{logo.jpg}\\[1.5cm]


    \HRule\\[0.4cm]
    {\huge\bfseries Project}\\[0.4cm]   
    \HRule\\[2.5cm]

    {\large\textit{Author}}\\
    Federico \textsc{Thiella}

    \vfill\vfill\vfill

    {\large\today}
    \vfill
\end{titlepage}

Per inserire del codice sorgente correttamente colorato e formattato, ho pensato di scrivere il codice in formato Markdown e di tradurlo in LaTex con Pandoc

pandoc codice.md -t latex

anche Pandoc sarà oggetto di un prossimo articolo!

Portable Tomcat

In questi giorni avevo necessità di testare nella mia macchina di sviluppo delle servlet utilizzando la versione portable di Apache Tomcat.

Di seguito la procedura che ho utilizzato.

Predisposizione Ambiente

Ho scelto di installare tutti i software portable sotto la directory sw della cartella dei documenti:

c:\Users\[username]\Documents\sw\

Ho quindi scaricato la versione 64-bit per Windows in formato zippato dalla sezione Download di Tomcat 10 ed ho estratto il contenuto in:

c:\Users\[username]\Documents\sw\apache-tomcat-10\

Il sistema richiede che sia definita la variabile di ambiente JRE_HOME, nel caso in cui non sia già configurata nel sistema è consigliabile predisporre il file setenv.bat all'interno della cartella bin come segue:

@echo off
set "JRE_HOME=c:\Program Files\Java\jdk-17.0.2\"
exit /b 0

Attivazione del server

Dato che si tratta di un ambiente di test, non ho necessità che il sistema sia sempre in esecuzione ma lo voglio lanciare quando necessario.

I comandi da lanciare sono pertanto, in sequenza, i seguenti:

setenv.bat
catalina.bat run

Mentre per terminare è sufficiente un normale CTRL+C.

Configurazione CmdEr

Dato che utilizzo il sistema CmdEr ho pensato di predisporre il seguente script che configura l'ambiente e lancia il server. L'ho chiamato catalina.cmd all'interno del seguente percorso C:\Users\[username]\Documents\sw\cmder\bin in modo che sia disponibile direttamente dalla console:

@echo on
pushd "c:\Users\[username]\Documents\sw\apache-tomcat-10.0.27\bin\"
call setenv.bat
call catalina.bat %*
popd

Configurazione utente Tomcat

Sotto la directory conf di Tomcat si trova il file di configurazione tomcat-users.xml al quale ho aggiunto, nella sezione users, la seguente definizione:

Accesso al sistema

Il sistema è accessibile quindi via browser al seguente indirizzo http://localhost:8080/.

L'installazione in ambiente di produzione richiede naturalmente una configurazione più attenta e curata (gestione privilegi utente, permessi file e directory, gestione servizi, gestione e rotazione log, ...) ma non è oggetto di questo articolo.

Recuperare una Password cifrata da Pentaho

Connessioni Database

Se si utilizza un repository centralizzato su database, Pentaho registra le informazioni legate alle connessioni (host_name, database_name, ..) nella tabella r_database, assieme alle credenziali in formato "offuscato". Non si tratta di un hash monodirezionale ma di un formato che, pur non direttamente leggibile, può essere facilmente decifrato.

select
  id_database,
  name,
  username,
  password
from
  r_database

Recupero password Offuscate

Le password offuscate sono precedute dalla stringa Encrypted. Se prendiamo come esempio la password prova, questa verrà salvata in tabella con la stringa Encrypted 2be98afc86aa7f2e4cb79ce60cc9db9db.

Quale metodo viene utilizzato per codificare e decodificare le password? Pentaho utilizza la libreria Encr.java che richiama diverse funzioni presenti in KettleTwoWayPasswordEncoder.java.

Un sistema per decifrare la password è dimostrato nella trasformazione Pentaho-Kettle-Password-Decrypt che lo trovo interessante perché permette di integrare la libreria Java che gestisce l'offuscamento delle password all'interno di una trasformazione, importanto la libreria e definendo la funzione da richiamare:

import org.pentaho.di.core.encryption.Encr;
Encr.decryptPassword(encrypted_password)

Tuttavia non è strettamente necessario utilizzare questo sistema.

Facendo una breve analisi del codice sorgente, si riesce facilmente ad individuare la logica del funzionamento, ovvero la definizione di una chiave "segreta":

public KettleTwoWayPasswordEncoder() {
    String envSeed = Const.NVL( EnvUtil.getSystemProperty( Const.KETTLE_TWO_WAY_PASSWORD_ENCODER_SEED ), "0933910847463829827159347601486730416058" ); // Solve for PDI-16512
    Seed = envSeed;
}

(che corrisponde alla costante KETTLE_TWO_WAY_PASSWORD_ENCODER_SEED nel caso essa sia configurata, oppure al valore fisso 0933910847463829827159347601486730416058)

e l'algoritmo di codifica/decodifica che, senza sorpresa, avviene semplicemente tramite OR esclusivo. Il codice che decifra la password, semplificando al massimo e togliendo diversi controlli, è sostanzialmente questo:

import java.math.BigInteger;

public class Main
{
        public static void main(String[] args) {
                String envSeed = "0933910847463829827159347601486730416058";
                String encrypted = "2be98afc807ce808daa1dab65d281bc8e";
                int RADIX = 16;

                BigInteger bi_confuse = new BigInteger( envSeed );
                BigInteger bi_r1 = new BigInteger( encrypted, RADIX );
                BigInteger bi_r0 = bi_r1.xor( bi_confuse );

                System.out.println(new String( bi_r0.toByteArray() ));
        }
}

"Dato un sufficiente numero di occhi, i bug tendono a zero" non funziona

Commento su un blog del 2022, in risposta a una discussione sulla Legge di Linus e sul ruolo del code review nell'open source.

L'idea che "dato un sufficiente numero di occhi, i bug tendono a zero" non funziona.

Trovare bug è un lavoro a tempo pieno, e spesso complesso. Non è che ti metti a leggere il codice sorgente sotto l'ombrellone e sei in grado di scoprire e risolvere i bug. Qualcuno forse, ma solo se il bug è banale o il codice è stato scritto da un principiante.

Il problema della Legge di Linus

La frase di Eric Raymond — "given enough eyeballs, all bugs are shallow" — è una semplificazione affascinante ma fuorviante. Funziona per i bug evidenti, quelli che si vedono leggendo il codice con attenzione. Non funziona per i bug che contano davvero.

I bug seri sono spesso:

  • di concorrenza, si manifestano solo in determinate condizioni di timing, e non si vedono leggendo il codice;
  • di stato, dipendono da una sequenza specifica di operazioni, che il lettore non può ricostruire senza eseguire il programma;
  • di casi limite, coinvolgono input che il lettore non ha motivo di considerare, perché non sono nel percorso principale;
  • di integrazione, emergono solo quando il codice interagisce con altri componenti, versioni diverse, ambienti diversi.

Questi bug non si trovano "guardando". Si trovano testando, strumentando, eseguendo. E il code review, da solo, non basta.

Il caso del codice offuscato

Se poi qualcuno vuole intenzionalmente offuscare del codice e nascondere delle funzionalità, il discorso cambia completamente.

Il code review diventa inefficace: il codice è scritto apposta per sembrare innocuo. Servono analisi dinamiche, reverse engineering, strumenti di tracing. E anche con tutto questo, dipende caso per caso: potrebbe essere molto difficile da scoprire.

Questo vale sia per il codice open source che per quello proprietario. Anzi, nel caso del codice proprietario la situazione è peggiore, perché non hai nemmeno la possibilità di leggere il sorgente.

Conclusione

La Legge di Linus ha un fondo di verità — più occhi possono trovare più bug — ma è una verità statistica, non una garanzia. E vale solo per i bug che si possono trovare leggendo. Gli altri richiedono lavoro, strumenti, e tempo.

Trovare bug è un mestiere. Non è una passeggiata sotto l'ombrellone.

Nomi di colonna con spazi: fastidio estetico o problema di sicurezza?

Commento su un blog, in risposta a una discussione sui nomi di colonna non convenzionali nei database.

La maggior parte dei sistemi di database lo permette, qualsiasi carattere può essere utilizzato per il nome della colonna, ma non è necessariamente una buona idea.

Generalmente mi danno un senso di poca professionalità, e se utilizzate con sistemi o framework poco professionali il disastro è servito (es. export su CSV senza utilizzare librerie adeguate per il CSV).

Se il sistema è solido però nessun problema, solo il fastidio estetico per delle query "brutte" e poco eleganti che necessitano di caratteri di quoting non standard tra i diversi db.

Anche se devo ammettere che di query brutte ce ne sono tante, e non è sempre il problema degli spazi...


Anni dopo, lavorando a filtersql, mi sono trovato ad affrontare lo stesso problema da un'altra angolazione: non più come "fastidio estetico", ma come vero e proprio problema di sicurezza.

La maledizione dei telefoni rotti

Commento del settembre 2021 su un blog, in risposta a un post sui telefoni e sulla loro fragilità.

In effetti stavo ricostruendo gli ultimi anni della mia vita:

  • ho chiuso il vecchio telefono nella portiera della macchina e si è crepato, pazienza era vecchio;
  • così ho comprato un nuovo telefono per mia moglie ed ho utilizzato il suo vecchio, dopo 4 mesi mi è caduto e si è rotto il vetro, pazienza era vecchio anche questo;
  • ne ho comprato uno di fascia bassa, mi è durato 8 mesi poi ho rotto il vetro, non valeva la pena cambiarlo;
  • anche mia moglie ha rotto il vetro, ma lo abbiamo fatto cambiare;
  • nel frattempo mi sono procurato un dual sim da utilizzare sia per casa che per lavoro;
  • dopo 6 mesi si è bruciato (era coperto da garanzia kasko);
  • me l'hanno cambiato con uno identico, dopo 4 mesi l'ho perso in giardino sotto una copiosa nevicata (era assicurato per danni ma non per smarrimento...), poi però dato che il giardino non era poi così grande, scavando bene l'ho ritrovato dopo 4 giorni perfettamente funzionante;
  • sembrava troppo bello per essere vero, infatti il mese dopo mi è caduto di nuovo, ma era assicurato;
  • il vetro cambiato del telefono di mia moglie sfarfallava... così ha comprato un telefono nuovo;
  • l'ultimo mio telefono dual sim per ora è in perfette condizioni, sono già passati 7 mesi, ma ho dovuto comprare una bruttissima e grossa custodia stile nokia anni '90, anche perché penso che l'assicurazione mi stia tenendo sotto controllo.

Ecco perché non ho alcuna intenzione di tenere in tasca un telefono da più di 1000 euro... ma anche 300 sono tanti, mi sento come se fossi in una stazione dei treni in mezzo a tanta gente con delle banconote che mi spuntano dalle tasche pronte a prendere il volo...

Lo stesso problema ce l'ho con occhiali, orologi, penne, biciclette ed ombrelli ovviamente.